Published
- 3 min read
How to deal with TypeScript Error 1337
How to deal with TypeScript Error 1337
You might have run into this error when trying to type something in TypeScript.
// An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.ts(1337)
I encountered this error when I tried to create an object containing only variables of type any
and mapping them to the correct type (for this example, the target type is number
)
TypeScript allows you to define a type like this:
type numberMap = { [x: string]: number } //Do not do this
Now this is a terrible type, You just opened the floodgates for potential Type Errors:
const exampleMap: numberMap = { a: 1 }
let valueA: number = numberMap.a // valid TypeScript - a is a number
let valueB: number = numberMap.b // valid TypeScript - b is undefined, have fun finding this bug
When you are using an index signature parameter ([x:string]) you should always type it as such:
type numberMap = { [x: string]: number | undefined }
However now we have to always check if the variable is undefined or not - Nobody has time for such nonsense. So let’s be smart and swap the generic string with a union of literal types.
type mapValues = 'a' | 'b'
const numberMap: { [x: mapValues]: number } = { a: 1, b: 2 } // Error
// An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.ts(1337)
At this point we get the ‘index signature (TS 1337)’ Error. We need to use a mapped object type. We have to write it like this:
type mapValues = 'a' | 'b'
const numberMap: Record<mapValues, number> = { a: 1, b: 2 }
This is a manually created map, now when I want to add, or remove any additional key - I will have to change the type and the value at the same time. So let’s continue and try to avoid this duplicate work:
const a: any = 1
const b: any = 2
const numberMapInternal = { a, b }
type mapValues = keyof typeof numberMapInternal // This creates the type 'a' | 'b'
export const numberMap = numberMapInternal as Record<mapValues, number>
Is this a good solution? No. It is overly complex, you need to know advanced TypeScript to understand what is happening.
If we step back, and think about it - the only reason why we need this construct is that variable a and b have the type ‘any’. If we would just type them with the correct type, TypeScript could automatically determine the correct type.
In my case, we wrote our own declaration file for our imported resources, there we accidentally declared the resources as any
. As soon as we corrected the declaration file, TypeScript automatically could figure out the correct type of the map.
Remember: The first solution may be the best solution, but it may be solving the wrong problem.