Typing Vue inject with object destructuring

Gustavo Oliveira
4 min readFeb 13, 2023

--

I recently started using the Composition API from Vue.js and I struggled to use typings with the Provide / Inject functionality.

Let's say I have the following components code and the injector component is a descendent of the provider one.

  • Provider component
<script lang="ts" setup>
import Injector from './Injector.vue';
import { provide, ref, type Ref } from 'vue';

const active: Ref<boolean> = ref(true);

const updateActive = (value: boolean): void => {
active.value = value;
}

provide('activeProvider', {
active,
updateActive
});
</script>

<template>
<p>This is the Provider component.</p>
<Injector />
</template>
  • Injector component
<script lang="ts" setup>
import { inject } from 'vue';

const { active, updateActive } = inject('activeProvider');
</script>

<template>
<p>This is the Injector component.</p>
<button @click="updateActive(!active)">Toggle active</button>
<pre>{{ active }}</pre>
</template>

Because I didn’t define a type for what the inject function returns, I would see the following errors in the code editor:

  • Property ‘active’ does not exist on type ‘unknown’.
  • Property ‘updateActive’ does not exist on type ‘unknown’.

The inject function has the following signatures:

// signature 1
function inject<T>(key: InjectionKey<T> | string): T | undefined;

// signature 2
function inject<T>(key: InjectionKey<T> | string, defaultValue: T, treatDefaultAsFactory?: false): T;

In the first signature, I can provide a Typescript generic to the inject function, but the return type can also be undefined. That is because there is no guarantee that this value will be provided at runtime.

For example, lets define an Active interface.

import type { Ref } from 'vue';

export default interface Active {
active: Ref<boolean>
updateActive: (value: boolean) => void
}

And use it in the inject function.

import Active from '@/types/Active';

const { active, updateActive } = inject<Active>('activeProvider');

If I use object destructuring there is a possibility of trying to extract properties from a undefined value, which would result in the following errors in the code editor:

  • Property ‘active’ does not exist on type ‘Active | undefined’.
  • Property ‘updateActive’ does not exist on type ‘Active | undefined’.

To fix this, I could:

  1. Assign a default value using the second parameter (signature 2)
import Active from '@/types/Active';

const { active, updateActive } = inject<Active>('activeProvider', {
active: false,
updateActive: () => undefined
});

Bear in mind that if for some reason the value is not provided at runtime, no warnings/errors will appear when the component is loaded. Also, post interactions on those values won’t have any effect. In many cases, it is best to have visibility on the error to easily address it beforehand, therefore this can be a disadvantage.

2. Force cast the returned value

import Active from '@/types/Active';

const { active, updateActive } = inject('activeProvider') as Active;

This way I'm explicitly eliminating the undefined return possibility. And if for some reason, the value is not provided at runtime, I would see a warning [Vue warn]: injection “activeProvider” not found. in the console. Also, the destructuring would fail.

Uncaught (in promise) TypeError: Cannot destructure property ‘active’ of ‘inject(…)’ as it is undefined.

Using InjectionKey

In the inject function signature you'll notice that the first parameter can be a string or an InjectionKey type, which has some advantages:

  1. Avoid potential name collisions
  2. Sync the type of the injected value between the provider and the injector

Here is an example:

First export the injection keys from another file:

import type { InjectionKey } from 'vue';
import type Active from '@/types/Active';

export const activeKey = Symbol() as InjectionKey<Active>;

And use it in the provider.

import { activeKey } from '@/types/injectionKeys';
import { provide, ref, type Ref } from 'vue';

const active: Ref<boolean> = ref(true);

const updateActive = (value: boolean): void => {
active.value = value;
}

provide(activeKey, {
active,
updateActive
});

An error occurs if the provide function receives an invalid payload.

Provider component

And the inject function is already typed.

Injector component

Notice that, because the value can still be undefined, if object destructuring is used, it is still necessary to add a default value or force cast.

import { activeKey } from '@/types/injectionKeys';
import type Active from '@/types/Active';
import { inject } from 'vue';

const { active, updateActive } = inject(activeKey) as Active;

--

--

Gustavo Oliveira
Gustavo Oliveira

Responses (1)