Deep Object Change Handlers in Typescript
Nick Scialli
May 22, 2020
Typescript’s ability to deeply-type objects is incredibly handy: it gives you confidence that you’re accessing the right keys on an object and that you’re using those keys as the right types. However, this typing doesn’t come for free: it can add complexity to things like change handlers. In this post, we’ll write a deep object change handler that both allows us to specify deep object types and satisfies the Typescript compiler.
A Sample Deep Object Type
Let’s use the following Settings
type as an example. It contains some visual settings about our app and some information about our user.
type Settings = {
display: {
mode: 'light' | 'dark';
};
user: {
name: string;
age: number;
admin: boolean;
};
};
We can then create a sample object that satisfies this type. Let’s use the following example.
const settings: Settings = {
display: {
mode: 'dark',
},
user: {
name: 'Betty',
age: 27,
admin: false,
},
};
Writing a Change Handler
So what if we want a change handler that will change any property two levels deep in this object? The secret lies in the use of Generics. We can specify that our key
is of type K
, where K extends keyof Settings
. Likewise, we can specify that our subkey
is of type S
, where S extends keyof Settings[K]
.
Putting this all together, we get the following change handler!
const updateSettings = <K extends keyof Settings, S extends keyof Settings[K]>(
key: K,
subkey: S,
value: Settings[K][S]
): Settings => {
const newSettings = {
...settings,
[key]: {
...settings[key],
[subkey]: value,
},
};
return newSettings;
};
And there we have it: a framework by which we can update deep types and keep our Typescript compiler happy!
Nick Scialli is a senior UI engineer at Microsoft.