reactjstypescriptvisual-studiomobxmobx-react

How to get Rid of Typescript Type error in the IDE regarding object properties


Background

I created a custom hook using React and MobX.

The custom hook receives an object units of type StoreUnits and also contains a useState hook that holds an object currentUnits of type StoreUnits.

Inside the custom hook I am iterating the over the object units, and if one of its properties is different than currentUnits im using the useState setter to update currentUnits.

The problem

I get a type error (in the IDE alone, not in run time) on the currentUnits[key] and units[key] even though I specified for both units and currentUnits are of type StoreUnits.

The Error

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'StoreUnits'.
  No index signature with a parameter of type 'string' was found on type 'StoreUnits'.ts(7053)
const currentUnits: StoreUnits

The code - custom hook

'use client'
import { useState, useEffect } from 'react'
import { autorun } from 'mobx'

type StoreUnits = {
  lengthUnit: 0 | 1 | 2 | 3
  pointForceUnit: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8
  stressUnit: 0 | 1 | 2 | 3
  areaUnit: 0 | 1 | 2
}

const useCurrentUnits = (units: StoreUnits) => {
    const [currentUnits, setCurrentUnits] = useState<StoreUnits>(units)
    
    useEffect(() => {
      autorun(() => {
        for (const [key, value] of Object.entries(units)) {
          if (currentUnits[key] !== units[key]) {
            setCurrentUnits({...units})
            break
          }
        }
      })
    }, [units])
  return {
    currentUnits
  }
}

export default useCurrentUnits

What I tried

I can specify something like currentUnits[key as 'lengthUnit'] and then the error will disappear, but it doesn't feel right because the key is not always 'lengthUnit'.


Solution

  • This is a problem with how Object.entries is currently typed (see Typescript Key-Value relation preserving Object.entries type). Right now, it will always return string as the type of the keys. Now, I'm not going to go in on why this is the case - there's already many explanations out there already - but to solve this problem, you'll need an assertion.

    There are other ways to get around this, but this is the easiest.

    for (const [key, value] of Object.entries(units) as [keyof StoreUnits, number][]) {
    //                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    Playground


    I do notice that you aren't even using the value (yet, at least), so in that case, you could just use Object.keys. This still needs the assertion though.

    Parentheses are important because of operator precedence here.

    for (const key of Object.keys(units) as (keyof StoreUnits)[]) {
    //                                   ~~~~~~~~~~~~~~~~~~~~~~~
    

    Playground