Defining interdependent props in Typescript for React Components

Aziz Khambati
3 min readDec 21, 2018

--

using Discriminated Unions.

This is not something new or fancy. Just writing because it’s not a common pattern for a lot of React Components.

Why?

For example, let’s say you have a date picker component, react-calendar, which provides you the functionality of doing both, selecting just a date or a date-range, which is controlled by the selectRange prop.

Simple Calendar Component Usage

We can define the props of Calendar like this:

Calendar Component Props

Here, I am defining the ValueType to be either a single Date or a tuple of two Dates (narrowing the definition constraint that it will have length two, instead of using Date[]). This works fine when I pass a single date in the value as props but typescript starts telling me that I haven’t handled the tuple case in the onChange function.

Typescript throws error that I have not handled the tuple case.

In older versions of Typescript (< 2.6) you could fix this error by defining the type of value in your onChange function. But this is a callback function, not a utility function that your code calls. So you should not be defining what parameters your function accepts but rather the Component should be defining what parameters will be provided. So the fix that you were applying was not a fix but a hack. This was allowed in older versions of Typescript because not a lot of libraries were typed and you needed function bi-variance. Read more about that here.

Now that the community has typed and covered a whole lot of cases we can get stricter with this and enable strictFunctionalTypes. This flag ensures a callback functions are checked against the parameters being passed by the caller. Which leads us to this error.

This can be fixed with a hack, which I would not recommend as you are literally bypassing Typescript’s checker.

Solution

Explicitly define the relation between objects using a technique called Discriminated Union.

Solution 1:

Extend the CommonProps

Solution 2:

Both are nearly identical.

Usage within Component

Here is usage example on how to narrow down types so that Typescript does not throw errors and check your code correctly.

Caveats

So there is one gotcha that you have to take care of, Typescript does not narrow down type of related props if you destructure this.props.

This is because inferred types do not propagate back to the source. Source.

So that is all I have to share. Go ahead type your components, increase Typescript adoption. Don’t write hacks like `value as Date`.

Further Reading

“Understanding TypeScript’s Structural Type System” by Drew Colthorp:

--

--