Dealing With Property Not Matching Index Signature
There is an interesting case in TypeScript when defining type definitions for objects. Imagine you have a large object where you know some of the properties are 100% static and always available. It may have the following shape:
type Translations = {
common: {
appName: string;
header: string;
footer: string;
};
// There will be other properties...
};
const obj: Translations = {
common: {
appName: `4markdown`,
header: `Some text`,
footer: `Some text`,
},
// There will be other properties...
};
How do you define a type definition for such an object? BTW, in this article, we will fix the common error message prompted by TypeScript: "Property is not matching index signature." Let's dive into the topic!
Understanding Index Signature Error
Your first idea might be to use the following syntax:
type TranslationNodeValue = Record<string, string>;
type Translations = {
common: {
appName: string;
header: string;
footer: string;
};
home: "Home page";
} & TranslationNodeValue;
const obj: Translations = {
common: {
appName: `4markdown`,
header: `Some text`,
footer: `Some text`,
},
home: "Home page",
};
However, this will produce a common error that frustrates many newcomers to TypeScript.
In the Translations
type, you have a property called common
, which is an nested object. At the top, you've defined the TranslationNodeValue
type, which is a simple object with string
values. By creating the Translations
type through an intersection (using the & operator) of these types, you're essentially saying: "Combine the shared parts of the two types to create a new type."
But, in our case, there is nothing common between the common
property type and TranslationNodeValue
type. So, this is how it looks (take a look at the empty space between) - this is our intersection result - an empty set.
So, now we know that the two types we want to merge into one with the intersection operator don't have anything in common. The TypeScript error is a little bit more developer-friendly and less mathematical - it just says that the common property is not compatible with Record<string, string>
, and that's true.
Fixing Index Signature Error
To ensure a smooth developer experience when working with such objects, we need to modify the type definition for the TranslationNodeValue
type. It must include the properties we already know, such as those defined for the common
property, as well as any new ones we want to include.
type TranslationNodeValue = Record<string, string | Record<string, string>>;
That's all there is to it! This change will work nicely from now on. Take a look at the gif below:
Here is the final showcase:
type TranslationNodeValue = Record<string, string | Record<string, string>>;
type Translations = {
common: {
appName: string;
header: string;
footer: string;
};
home: string;
other: string;
} & TranslationNodeValue;
const obj: Translations = {
common: {
appName: `4markdown.com`,
header: `Some text`,
footer: `Some text`,
},
home: "Home page",
other: "Test",
otherOther: {
other: "test",
},
};
So, what exactly did we do? We added a shared part for both types. Now, the TranslationNodeValue
type includes the shape of the common
property type - an object specified as Record<string, string>
. Thanks to that, there is now a shared part between them.
It's really important to understand that after adding new properties with different values that are known from the start, such as someOtherProp: number
, you will need to add number
to the type definition you're intersecting with. Whenever a new property is added to the base object (the properties you know), you need to include the type values of each in the wider type (TranslationNodeValue
).
Summary
Now you know how to create fancy type definitions for complex object structures thanks to the intersection operator. While it's used in rare cases, it can dramatically improve type definitions across your project. Consider using this technique if you know some of the object properties, but others may have unknown keys and the same repetitive value signature.