Type safe updates using type inference
February 19, 2020
A common problem we see when creating forms in React is the need for a generalised method that will update the form values when an input changes.
For example, we might need to change the following object:
When using input fields or components in react, we can pass the value of the input to an update method and specify the property to update:
This works but it isn't type safe. This means that we could pass a value that is not appropriate for the property, or even try to update a property that does not exist on the object (spelling mistake or otherwise):
Index Typing
To help with this problem we can leverage some advanced typescript features that allow us to construct a generic method that will require us to specify only values of properties that exist for a type, as well as the correct types of values to assign to those properties:
In the above method we are using a generic type T
as the source object, a key of this type K
and the value to assign it which is then required to be of index type T[K]
. The method is then used like this:
T
becomes type FormObject
. The key
parameter will then only accept valid properties of T
, such as DateOfBirth
in this case. The value
parameter will only accept the correct type for the key
, which in this case is a Date
. Our method returns a 'spread' of the object which creates a new instance, this helps play nice with set
hooks.
We can then use this generic method in our input components and can wrap it in a set function if needed:
Mapped and Conditional Typing
We can expend this concept to make our method only allow us to update fields of a certain type. For example, if we had a user picker that returned a string Id
and multiple user fields on an object, we only want to be able to update those user fields from this component.
To do this we leverage mapped and conditional types in typescript:
Here we have created a new type that requires an object T
and a second parameter as the type that we want to specify, V
. This returns a set of properties that use K
in T
.
The type definition takes the generic objects keys K
and then looks for types that match the type V
, from which it conditionally returns either the property of the specified type or never
. It then filters this list to only return valid properties (i.e. removes all never
entries).
We can then use this type in a generic method to only allow mutation of the specified properties:
Using the method
The above method only allows changes to properties that are of type User
and only accepts string
values. In our user picker example the usage might be something like:
The fact that our type safe method only allows us to update specific User
fields on an object means we end up with fewer mistakes, that in some cases, can be quite difficult to debug.