What could be causing the type error in Vue 3.3 when using a generic v-for key?

My application is built on Vue 3.3.4 with the latest support for generics in single file components. One of the components I'm working on is a generic list, which iterates over a set of items passed as a prop. There is also a prop called itemKey, used for keying the v-for loop.

<script setup lang="ts" generic="T, K extends keyof T">
defineProps<{
  items: Array<T>
  itemKey: K
}>()
</script>

<template>
  <ul>
    <li v-for="item in items" :key="item[itemKey]">
      <slot :item="item" />
    </li>
  </ul>
</template>

When I try to use :key="item[itemKey]", it's causing an error to be highlighted with the message:

Type '[{ type: PropType<K>; required: true; }] extends [Prop<infer V, infer D>] ? unknown extends V ? IfAny<V, V, D> : V : { type: PropType<K>; required: true; }' cannot be used to index type 'T'.

Even though the error is only a type issue, the code itself still functions correctly. Any ideas why my itemKey is causing this misinterpretation?


TEMPORARY FIX: Add an inline //@ts-ignore in the :key prop.

<li
  v-for="(item, i) in items"
  :key="
    //@ts-ignore
    item[itemKey]
  "
>

Answer №1

Thoughts on your code

https://i.sstatic.net/QA62T.png

The reason for this issue is that the type of itemKey is generic in the component definition and is only known at runtime. As a result, TypeScript's static type checking is unable to pinpoint the exact key it is referring to.

To resolve this issue, a more specific type definition for items[] is required.

https://i.sstatic.net/76WEP.png

In Vue, the :key attribute is designed to work with string, number, or symbol values. Therefore, if the type of item[itemKey] matches one of these types, it can be used as the key; otherwise, it cannot.


Possible Solution # 1 - T extends U ? U : never - TypeScript 2.8 and higher

In this scenario, we assume that every value in the object T can serve as a valid key. By utilizing item[itemKey], we are essentially stating this assertion, which leads to the error.

Array<{ [key in keyof T]: T[key] extends string | number | symbol ? T[key] : never }>

The items prop is an array that, based on the keys (keyof T) of type T, selects the types from T[key] that are either string, number, or symbol. If the types do not match, the never type is returned.

This ensures that only string, number, or symbol values can be assigned to the :key attribute.

<script setup lang="ts" generic="T, K extends keyof T">
defineProps<{
  items: Array<{ [key in keyof T]: T[key] extends string | number | symbol ? T[key] : never }>
  itemKey: K
}>()
</script>

<template>
  <ul>
    <li v-for="item in items" :key="item[itemKey]">
      <slot :item="item" />
    </li>
  </ul>
</template>



Possible Solution # 2 (declare item[itemKey] type)

If the object can contain values of various types, performing precise type checking may not be worth the effort. It is recommended to declare `item[itemKey]` as a value that aligns with our expectations for future use. You may also consider implementing runtime JavaScript checks to ensure the type of `item[itemKey]` aligns with your expectations and display error messages in the console during development mode.

<script setup lang="ts" generic="T, K extends keyof T">
defineProps<{
  items: Array<T>
  itemKey: K
}>()
</script>

<template>
  <ul>
    <li v-for="item in items" :key="(item[itemKey as K] as string | number | symbol)">
      <slot :item="item" />
    </li>
  </ul>



Possible Solution # 3 (convert to string)

If ensuring a string value for the :key attribute is crucial, simply convert the value of item[itemKey] to a string. By explicitly converting it to a string, TypeScript won't raise any issues as it meets the requirements of the :key attribute, regardless of the actual type of item[itemKey].

<script setup lang="ts" generic="T, K extends keyof T">
defineProps<{
  items: Array<T>
  itemKey: K
}>()
</script>

<template>
  <ul>
    <li v-for="item in items" :key="String(item[itemKey as K])">
      <slot :item="item" />
    </li>
  </ul>
</template>

Answer №2

The issue you're facing appears to stem from the TypeScript compiler struggling to infer types within Vue's template block. It's not a matter of misinterpreting your itemKey, but rather TypeScript's difficulty in determining the appropriate type in the template context.

When TypeScript encounters :key="item[itemKey]", it seems unable to recognize that itemKey is a valid key of the item object. This could be due to a lack of full synchronization between the Vue template compiler and TypeScript at the moment.

In addition to using @ts-ignore, an alternative solution might involve creating a method or computed property in the script block to handle the keying operation, which can then be used in the template.

<script setup lang="ts" generic="T, K extends keyof T">
const props = defineProps<{
  items: Array<T>
  itemKey: K
}>()

const getItemKey = (item: T) => item[props.itemKey];
</script>

<template>
  <ul>
    <li v-for="item in items" :key="getItemKey(item)">
    <slot :item="item" />
  </li>
</ul>
</template>

This approach effectively relocates the TypeScript-challenging operation from the Vue template to the script block, where TypeScript's type inference capabilities are more dependable.

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

How can I retrieve the decimal x and y coordinates when the mouse is moved in Typescript Angular?

I am in the process of transitioning my user interface from Flash/Flex, where it stores values in decimal format. I need to access and reuse these values. Here is a demo showcasing my problem. Here is a snippet: import { Component, Input } from '@an ...

Typescript encounters transpilation issues when the spread operator is omitted for undefined values {...undefined}

I am currently working on a TypeScript project where I have encountered a peculiar issue. Within some of my TypeScript files, I am including a plain JavaScript/Node file named config.js. The content of config.js is as follows: 'use strict'; modu ...

I encountered an issue while trying to implement a custom pipe using the built-in pipe

My custom pipe seems to be functioning well, except for the built-in pipes not working within it. I've imported Pipe and can't figure out what could be causing the issue. Below is the code with the errors: import { Pipe, PipeTransform } from &a ...

Is there a way to modify a single object within an array?

Here is the HTML representation of my data: https://i.sstatic.net/VbKQ4.png page.html <ul id="elements"> <li *ngFor="let elem of fetchdata" (click)="log(elem)"> {{elem.title}} {{elem.description}} </li> ...

"Concealing a column in a PrimeNG data table with dynamic columns: A step-by-step

Hi, I'm looking for a way to hide a column in my PrimeNG data table. Is there an attribute that can be used to turn off columns in PrimeNG data tables? .Html <p-dataTable emptyMessage="{{tbldatamsg}}" [value]="dataset" scrollable="true" [style]=" ...

The Vue components in Laravel fail to update after pulling changes from the repository on the live server

Currently working with LARAVEL and Vue, I recently deployed a project on Digital Ocean using Ubuntu and Nginx. After launching, I have added more features to the project. However, when I pulled these changes onto the production server, they are not appeari ...

Adding or removing items from the reactive source in a v-list with Vuetify causes the list to collapse

I'm experiencing an issue with reactivity in my Vuetify and Meteor.js setup. Whenever the number of items in a sub-group changes, the entire list collapses, which is quite frustrating since I have to navigate back through multiple levels to get to the ...

How can I send 'blocking' parameter to getStaticPaths function in Next.js using TypeScript?

Whenever I try to use fallback: 'blocking', in my Next.js page, TypeScript throws an error. I am using the latest 12.2.0 version of Next.js. What could be causing this issue? Type '() => Promise<{ paths: any; fallback: string; }>&ap ...

Transform a JSON array containing individual objects into a new JSON array with objects

My array contains objects with nested objects in each element, structured like this: [ { "person": { "name": "John", "isActive": true, "id": 1 } }, { "person": { "name": "Ted", "isActive": true, "id": 2 } } ] I ...

Collection of assorted objects with varying sizes that are all subclasses of a common superclass

I need to create an array that can hold any number of items, all of which are subclasses of the same base class. The issue I'm facing is with type checking in TypeScript - when I declare the array as Array<BaseClass>, I have to cast each item to ...

Expand the data retrieved from the database in node.js to include additional fields, not just the id

When creating a login using the code provided, only the user's ID is returned. The challenge now is how to retrieve another field from the database. I specifically require the "header" field from the database. Within the onSubmit function of the for ...

Invalidity of types occurs when dispatching data to redux

My reducer code is as follows: import { profileAPI } from '../api/api' import shortid from 'shortid' const ADD_POST = 'profile/ADD-POST' const SET_USER_PROFILE = 'profile/SET_USER_PROFILE' const SET_STATUS = 'p ...

Vue component does not reflect changes made to Select2 form input value

I require assistance. Initially, I was able to successfully pass data using a standard select form. However, when I switched to select2, the value does not change as expected. Why is my select2 not updating the value like the regular select form? This is ...

Unable to locate a type definition file for module 'vue-xxx'

I keep encountering an error whenever I attempt to add a 3rd party Vue.js library to my project: Could not find a declaration file for module 'vue-xxx' Libraries like 'vue-treeselect', 'vue-select', and 'vue-multiselect ...

Unable to append item to document object model

Within my component, I have a snippet of code: isLoaded($event) { console.log($event); this.visible = $event; console.log(this.visible); this.onClick(); } onClick() { this.listImage = this.imageService.getImage(); let span = docu ...

Determining the appropriate generic type in Typescript

In my code, there is a method designed to extend an existing key-value map with objects of the same type. This can be useful when working with database query results. export function extendWith< T extends { id: string | number }, O = | (T[" ...

Incorporating tawk.to into a Nuxt/Vue application

Has anyone had success implementing tawk.to in a Nuxt application? I took the initiative to create a file called "tawk.js" in my plugin folder and added the following code: var Tawk_API = Tawk_API || {}, Tawk_LoadStart = new Date() (function () { ...

Error: Uncaught TypeError - Unable to access 'reduce' property of undefined value

Currently, I am focusing on implementing yup validation. Specifically for FileList validation, encountering an issue where leaving the input empty triggers the following error message: enter image description here Below is the code snippet in question: (C ...

Troubleshooting TypeScript errors in a personalized Material UI 5 theme

In our codebase, we utilize a palette.ts file to store all color properties within the palette variable. This file is then imported into themeProvider.tsx and used accordingly. However, we are encountering a typescript error related to custom properties as ...

Can we programmatically switch to a different mat-tab that is currently active?

Looking to programmatically change the selected mat-tab in a TypeScript file. I came across a solution for md-tab, but I need it for mat-tab. I attempted the suggested solution without success. Here's what I tried: HTML <button class="btn btn- ...