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!
Top comments (0)