TypeScript Errors with Dynamic Properties
This warning has been showing up in the Vercel build logs for my Habit Tracker app I’ve been working on:
1:38 Warning: Unexpected any. Specify a different type. @typescript-eslint/no-explicit-any
The refers to the helper function that computes the totals of each habit to be displayed in the table footer.
export function returnTotal(records: any[] | undefined, habit: string) { return records?.reduce((accumulator, currentValue) => { return accumulator + currentValue[habit]; }, 0); }
I have an existing type for records called RecordType
export interface RecordType { date: string; dateAsNumber: number; jacks: number; meditation: number; pullups: number; pushups: number; situps: number; stairs: number; }
So I can just type that parameter like this
records: RecordType[]
As soon as I do that though, I get
TS7053: Element implicitly has an any type because expression of type string can't be used to index type RecordType No index signature with a parameter of type string was found on type RecordType
which refers to the currentValue[habit]
expression in the return statement.
This took a little while to figure out, but eventually I learned (or possibly relearned) that you have to do
something special in TypeScript when dynamically accessing Object properties. TypeScript doesn’t just let you use
a parameter with type string
to index your Object. You need to come up with something that has the keyof
type.
With the help of this [StackOverflow question](https://stackoverflow. com/questions/62438346/how-to-dynamically-access-object-property-in-typescript), I refactored to:
export function returnTotal(records: RecordType[] | undefined, habit: string) { return records?.reduce((accumulator, currentValue) => { return accumulator + currentValue[habit as keyof typeof currentValue]; }, 0); }
Which yields one more TS error:
TS2365: Operator + cannot be applied to types number and string | number
pointing to accumulator + currentValue[habit as keyof typeof currentValue]
in the return. This is because the
RecordType has one property of type string.
Refactoring that line to
accumulator + (currentValue[habit as keyof typeof currentValue] as number)
clears the final TS error and the build warning is gone.
Final working function:
export function returnTotal(records: RecordType[], habit: string) { return records?.reduce((accumulator, currentValue) => { return accumulator + (currentValue[habit as keyof typeof currentValue] as number); }, 0); }
This was a great little exercise in dealing with Object and TypeScript errors.
Update:
Shortly after publishing this, Justin from Virtual Coffee read this and provided a
refactor to help drop the usage of the as
keyword, which is problematic. I really like how
CountableHabits
strips out the properties that I don’t want to count and TypeScript can’t count. It makes for a concise final solution. Thanks
Justin!
type CountableHabits = Omit<RecordType, "date" | "dateAsNumber">; export function returnTotal(records: RecordType[], habit: keyof CountableHabits) { return records?.reduce((accumulator, currentValue) => { return accumulator + currentValue[habit]; }, 0); }