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'.
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][]) {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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)[]) {
// ~~~~~~~~~~~~~~~~~~~~~~~