Using Typescript inject with Vue 3 is currently not functioning as expected. The issue arises when trying to create spread types from object types

When using Vue 3 and TypeScript, an error is encountered (as shown below) only when the script lang="ts" attribute is present. Can someone provide insight into why the inject function in Vue 3 with TypeScript does not function correctly?

ERROR in src/components/global/HeaderMenu.vue:85:7
TS2698: Spread types may only be created from object types.
    83 |     const auth = inject('Auth');
    84 |     return {
  > 85 |       ...auth,
       |       ^^^^^^^
    86 |     };
    87 |   },
    88 | });

Working example:

<script>
import { defineComponent } from 'vue';
import { inject } from 'vue';
export default defineComponent({
  name: 'HeaderMenu',
  inject: ['Auth'],
  methods: {
    login() {
      this.Auth.loginWithRedirect();
    },
    logout() {
      this.Auth.logout();
      this.$router.push({ path: '/' });
    },
  },
  setup() {
    const auth = inject('Auth');
    return {
      ...auth,
    };
  },
});
</script>

Example that generates the above error:

<script lang="ts">
import { defineComponent } from 'vue';
import { inject } from 'vue';
export default defineComponent({
  name: 'HeaderMenu',
  inject: ['Auth'],
  methods: {
    login() {
      this.Auth.loginWithRedirect();
    },
    logout() {
      this.Auth.logout();
      this.$router.push({ path: '/' });
    },
  },
  setup() {
    const auth = inject('Auth');
    return {
      ...auth,
    };
  },
});
</script>

Main.js

import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
// Auth Service
import { Auth0 } from '@/auth';

import BootstrapVue3 from 'bootstrap-vue-3';
// Import Bootstrap an BootstrapVue CSS files (order is important)
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap-vue-3/dist/bootstrap-vue-3.css';


async function init() {
  const AuthPlugin = await Auth0.init({
    onRedirectCallback: (appState) => {
      router.push(appState && appState.targetUrl ? appState.targetUrl : window.location.pathname);
    },
    clientId: 'xxxx',
    domain: 'xxxx',
    audience: process.env.VUE_APP_AUTH0_AUDIENCE,
    redirectUri: window.location.origin,
  });
  const app = createApp(App);
  app
    .use(AuthPlugin)
    .use(router)
    .use(BootstrapVue3)
    .mount('#app');
}

init();

Example of the auth.js

 import createAuth0Client, {
    Auth0Client,
    GetIdTokenClaimsOptions,
    GetTokenSilentlyOptions,
    GetTokenWithPopupOptions,
    LogoutOptions,
    RedirectLoginOptions,
    User
} from '@auth0/auth0-spa-js'
import {App, Plugin, computed, reactive, watchEffect} from 'vue'
import {NavigationGuardWithThis} from "vue-router";

let client: Auth0Client;

interface Auth0PluginState {
    loading: boolean,
    isAuthenticated: boolean;
    user: User | undefined,
    popupOpen: boolean;
    error: any
}

const state = reactive<Auth0PluginState>({
    loading: true,
    isAuthenticated: false,
    user: {},
    popupOpen: false,
    error: null,
})

async function handleRedirectCallback() {
    state.loading = true;

    try {
        await client.handleRedirectCallback();
        state.user = await client.getUser();
        state.isAuthenticated = true;
    } catch (e) {
        state.error = e;
    } finally {
        state.loading = false;
    }
}

function loginWithRedirect(o: RedirectLoginOptions) {
    return client.loginWithRedirect(o);
}

function getIdTokenClaims(o: GetIdTokenClaimsOptions) {
    return client.getIdTokenClaims(o);
}

function getTokenSilently(o: GetTokenSilentlyOptions) {
    return client.getTokenSilently(o);
}

function getTokenWithPopup(o: GetTokenWithPopupOptions) {
    return client.getTokenWithPopup(o);
}

function logout(o: LogoutOptions) {
    return client.logout(o);
}

const authPlugin = {
    isAuthenticated: computed(() => state.isAuthenticated),
    loading: computed(() => state.loading),
    user: computed(() => state.user),
    getIdTokenClaims,
    getTokenSilently,
    getTokenWithPopup,
    handleRedirectCallback,
    loginWithRedirect,
    logout,
}

const routeGuard: NavigationGuardWithThis<undefined> = (to: any, from: any, next: any) => {
    const {isAuthenticated, loading, loginWithRedirect} = authPlugin;

    const verify = async () => {
        // If the user is authenticated, continue with the route
        if (isAuthenticated.value) {
            return next();
        }

        // Otherwise, log in
        await loginWithRedirect({appState: {targetUrl: to.fullPath}});
    }

    // If loading has already finished, check our auth state using `fn()`
    if (!loading.value) {
        return verify();
    }

    // Watch for the loading property to change before we check isAuthenticated
    watchEffect(() => {
        if (!loading.value) {
            return verify();
        }
    })
}

interface Auth0PluginOptions {
    domain: string,
    clientId: string,
    audience: string,
    redirectUri: string,

    onRedirectCallback(appState: any): void
}

async function init(options: Auth0PluginOptions): Promise<Plugin> {
    client = await createAuth0Client({
        // domain: process.env.VUE_APP_AUTH0_DOMAIN,
        // client_id: process.env.VUE_APP_AUTH0_CLIENT_KEY,
        domain: options.domain,
        client_id: options.clientId,
        audience: options.audience,
        redirect_uri: options.redirectUri,
    });

    try {
        // If the user is returning to the app after authentication
        if (
            window.location.search.includes('code=') &&
            window.location.search.includes('state=')
        ) {
            // handle the redirect and retrieve tokens
            const {appState} = await client.handleRedirectCallback();

            // Notify subscribers that the redirect callback has happened, passing the appState
            // (useful for retrieving any pre-authentication state)
            options.onRedirectCallback(appState);
        }
    } catch (e) {
        state.error = e;
    } finally {
        // Initialize our internal authentication state
        state.isAuthenticated = await client.isAuthenticated();
        state.user = await client.getUser();
        state.loading = false;
    }

    return {
        install: (app: App) => {
            app.provide('Auth', authPlugin);
        },
    }
}

interface Auth0Plugin {
    init(options: Auth0PluginOptions): Promise<Plugin>;
    routeGuard: NavigationGuardWithThis<undefined>
}

export const Auth0: Auth0Plugin = {
    init,
    routeGuard
}

Answer №1

The compiler is unable to determine the type of auth from the inject(...) call because it cannot link it to the corresponding provide(...) call. In order for the compiler to understand the type, it must be explicitly provided with type information.

The type of authPlugin needs to be made accessible, for example:

export type TAuthPlugin = typeof authPlugin;

This type information then needs to be passed to the inject function. Typically, functions like inject are designed to be generic in nature:

const auth = inject<TAuthPlugin>('Auth');

Answer №2

It appears that there may be an issue with how you are utilizing the inject feature in your code (refer to https://v3.vuejs.org/guide/composition-api-provide-inject.html#using-inject). Consider removing the inject property altogether.

Additionally, it is unclear how exactly you are accessing the inject properties - either through the Auth injected object or via the spread properties of the Auth object.

To address TypeScript type checking errors, consider specifying the type of the injected property as 'Auth0Plugin', making sure to export the necessary interface beforehand.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for identifying the most effective element locator in the DOM with Playwright

Currently, I am in the process of incorporating Playwright tests for a website that supports multiple locales. The majority of the page content is dynamically generated from CMS content (Contentful). I am hesitant about using hardcoded text locators like ...

Animating the Click Event to Change Grid Layout in React

Can a grid layout change be animated on click in React? For instance, consider the following component: import { Box, Button, styled, useMediaQuery } from "@mui/material"; import Row1 from "./Row1"; import React from "react"; ...

``A bidirectional data exchange mechanism enabling seamless communication between a parent and child

Currently, I am in the process of learning VueJS and experimenting with building a basic application that involves listing, viewing, creating, editing, and deleting stories. Each story consists of a title, content (text only), and a datetime stamp. So far, ...

Display the React component following a redirect in a Next.js application that utilizes server-side rendering

Just starting out with next.js and encountering a problem that I can't seem to solve. I have some static links that are redirecting to search.tsx under the pages folder. Current behavior: When clicking on any of the links, it waits for the API respo ...

Removing the final element within a nested array: a step-by-step guide

let originalArray=[ [ "Test1", "4", "160496" ], [ "Test2", "6", "38355" ], [ "Test3", "1", "1221781" ], [ " ...

Here is a unique rewrite: "Strategies for effectively passing the data variable in the geturldata function within Vue.js on the HTML side vary

How can I properly pass a variable in the getdataurl function in Vue.js? I need help with passing a variable in getdataurl function in Vue.js. Please provide a clear explanation and include as much detail as possible. I have tried doing some background r ...

Is it possible to dynamically pass a component to a generic component in React?

Currently using Angular2+ and in need of passing a content component to a generic modal component. Which Component Should Pass the Content Component? openModal() { // open the modal component const modalRef = this.modalService.open(NgbdModalCompo ...

How can the ordering of dynamically generated components be synchronized with the order of other components?

Currently, I'm delving into Vue 3 and encountering a specific issue. The tabs library I'm using only provides tab headers without content panels. To work around this limitation, I've come up with the following solution: <myTabs/><!- ...

Tips for efficiently displaying a computed property on a template using Vuetify?

I am attempting to display only the initials of a user who is logged in while inside a store. Here is my template: <v-menu v-if="this.$store.getters.getLoggedUser"> <template v-slot:activator="{ on, attrs, userInitials }&quo ...

Subtracting Arrays Containing Duplicates

Imagine having two arrays defined like this: const A = ['Mo', 'Tu', 'We', 'Thu', 'Fr'] const B = ['Mo', 'Mo', 'Mo', 'Tu', 'Thu', 'Fr', 'Sa&ap ...

Using TypeScript to narrow down types within mapped types

Can you create a mapped type based on the property type? For example, if I want to map all properties with type String to Foo and all other types to Bar. Can this be done like this: type MappedType<T> = { [P in keyof T]: T[P] === String ? Foo : B ...

Leveraging the power of the map function to manipulate data retrieved

I am working on a nextjs app that uses typescript and a Strapi backend with graphql. My goal is to fetch the graphql data from strapi and display it in the react app, specifically a list of font names. In my react code, I have a query that works in the p ...

Mapping arguments as function values

Hello there, I have an array of objects that I am attempting to map through. const monthObject = { "March 2022": [ { "date": "2022-03-16", "amount": "-50", &q ...

Having trouble getting your Vue Tailwind project to start due to a postcss error?

I am experiencing an issue while trying to run my project on my laptop. The project works fine on all other devices with the same code, but for some reason, I am unable to run tailwind CSS on it. The error message I'm receiving is as follows: in ./sr ...

How does VueJS compare to Angular Service functionality?

I am looking to consolidate all my server communication functions and data fetching utilities into a single reusable file within VueJS. Plugins don't seem like the ideal solution. Perhaps I should consider template-less components instead? ...

typescript: best practices for typing key and value parameters in the forEach loop of Object.entries()

I have a specific object with key/value pairs that I need to iterate over using the entries() method of Object followed by a forEach() method of Array. However, I'm struggling to understand how to avoid a typescript error in this situation: type objTy ...

Mobile streaming denied by video element in Vue.js app

I'm currently developing a Vue.js web application that requires video streaming capabilities. The backend is powered by a Node.js application, which retrieves videos from an S3 bucket and sends an unbuffered stream to the client. Below is the frontend ...

The process of assigning a function to an object in JavaScript is currently not functioning properly

After updating a Vue2 project to Vue3, I ran into an issue with Javascript. It seems that the language now prevents me from assigning a function to an object. In the code below, I define a function "bar" within a loop. While I can successfully call the fu ...

Utilizing VueJs @error handler for managing broken image links

I am encountering a strange issue with the VueJS @error handler. My goal is to hide images with broken links and display a placeholder instead. However, when I have two images with broken links, only the first image displays the placeholder while the other ...

Tips for utilizing the JS attribute "offsetWidth" within Angular 4

I am attempting to retrieve the width of an element using JavaScript in my Angular application. document.getElementsByClassName("element")[0].offsetWidth; However, I keep encountering the following compilation error: Property 'offsetWidth' d ...