Stricter type checking for spreaded props

Updated: Jul 7, 2024
Published: Jul 6, 2024
Tags: typescript, react, generics

The Problem

Say we have a FunctionComponent that is responsible for automagically rendering the correct set of children based on the value of a particular prop it is passed.

If that is all this component is responsible for, then there is really no problem here. The complication starts when new requirements are introduced and this component is now supposed to accept props and forward them to the children it is rendering.

In an ideal world we would eliminate the props-forwarding as a pattern altogether, but let us assume that for this particular case we need to do it.

In order to accept any props and then forward them, we try the first escape hatch that comes to mind: declaring props as `any` type and then spreading them on to the children that rightfully should be receiving them.

Now this works, but it is less than ideal. This is literally us 'turning off TypeScript' and taking matters into our own hands. When has that ever gone wrong? 😅

In mature organisations one would not be able to even commit this kind of code, let alone push it upstream. The linters and pre-commit hooks would never tolerate such recklessness.

The Solution: Generics + Type utilities

Our problem is replacing any with something meaningful. We want it to be dynamic (based on the shape prop) but still within a known boundary (one of CircleRendererProps or PolygonRendererProps).

Consider the following changes to the code:

These changes do exactly what we wanted to do. We replaced any with a generic-type (ShapePropsType) which falls within a boundary (T extends Shape). We also made the shape prop generic and also conform to the same boundary.

This means the TypeScript interpretation of our interfaces is now as per the following:

Perfect! The changes to the ShapeRenderer FunctionComponent signature are simply taking advantage of this generic interface. Its shapeProps will now automatically be of the right type based on the value of the shape prop.

The last piece of the puzzle was the following type assertion:

It is simply narrowing down the type of props accepted by ShapeComponent to the exact typeof shapeProps - which is now guaranteed to be accurate based on the value of shape.

This is the end result:

This will also keep working as the project grows and more entries are added to the shapeRendererMap.

Conclusion

  • Generics are awesome
  • TS Utility types are awesome
  • Spreading props is okay as long as we are type-checking properly