4

I’m new in TypeScript and trying to use it with Vue 3 composition API and provide / inject. Let's say in parent component A I have something like this:

// Parent component A

import { provide, ref } from 'vue';
import ScoreType from "@/types/Score";

setup() {
  ..
  const score = ref<ScoreType[]>([]);
  const updateScore = (val: ScoreType) => {
    score.value.push(val);
  };

  provide('update_score', updateScore);  
  ..
}

...and then want to inject updateScore function in child component B to be able to update values in parent component A (this is what docs recommend). Unfortunately, I get a TS error Object is of type 'unknown'

// Child component B

import { inject } from 'vue';

setup() {
  ..
  const updateScore = inject('update_score');
  const checkAnswer = (val: string) => {
    updateScore({ /* ScoreType object */ });  // → Object is of type 'unknown'.
  }
  ..
}

What should I do to fix the TypeScript error? I couldn't find any examples about injecting update functions in TS.

1 Answer 1

5

Let's firstly declare a type for our updateScore() function

// @/types/score.ts
export type ScoreType = { points: number };

export type UpdateScoreFunction = (val: ScoreType) => void;

Now we need to declare an InjectionKey which will hold the type information of our provided/injected variable (function in this case). More about it in Vue docs

Let's make a separate folder to store our keys and to keep the things organized:

// @/symbols/score.ts
import { InjectionKey } from "vue";
import { UpdateScoreFunction } from "@/types/score";

export const updateScoreKey: InjectionKey<UpdateScoreFunction> = Symbol("updateScore");

In our parent component (A):

<script lang="ts">
import { defineComponent, provide, ref } from "vue";

import { ScoreType, UpdateScoreFunction } from "@/types/score";
import { updateScoreKey } from "@/symbols/score";

export default defineComponent({
  setup() {
    const score = ref<ScoreType[]>([]);
    
    // Actually, adding ': UpdateScoreFunction' is optional 
    const updateScore: UpdateScoreFunction = function (val: ScoreType) {
      score.value.push(val);
    };

    // Replace the string with InjectionKey
    provide(updateScoreKey, updateScore);

    // ...
  },
});
</script>

In our child component (B):

<script lang="ts">
import { defineComponent, inject } from "vue";
import { updateScoreKey } from "@/symbols/score";

export default defineComponent({
  setup() {

    // Replace the string with InjectionKey
    const updateScore = inject(updateScoreKey);

    // In case the `updateScoreKey` is not provided by the parent component..
    if (updateScore === undefined) {
      throw new Error('Failed to inject "updateScore"');
    }

    const checkAnswer = (val: string) => {

      // ...

      // The error is gone
      updateScore({ 
        points: Math.floor(Math.random() * 100),
      });
    };

    // ...
  },
});
</script>

Working example provided here: codesandbox.io/s/so-provide-inject

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, it works. But why do we have to handle possibility of injected function being 'undefined'? I can't imagine scenario when parent (in my app) doesn't have the injected function. Is it related with composition API and composing components from "multiple pieces" maybe?
It will be undefined if you remove provide(updateScoreKey, updateScore); from your parent component, because there will be nothing to inject in the child one. If you don't want to handle it, you can pass a second parameter to the inject() function in your child component to specify the default value: const updateScore = inject(updateScoreKey, function() { ... });.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.