Using TypeScript and conditional props, you can build an API for your React components that is clear and explicit about its prop requirements. Doing so will help your team and will set you apart as a rock-star developer.
We will start with a simple use case, and we will finish up with a more advanced one that involves generics.
This tutorial assumes you have a basic knowledge of TypeScript and types.
One or the other
Imagine you have a component where you have two conflicting properties. You want the client to provide either one property or the other, but not both.
For our example, we will use an Avatar component that accepts either a URL string or a file object as a source. If the client provides the URL string, we do not want them to provide the file too, and vice-versa.
Here’s what the first iteration of your component might look like:
Right now, if we pass both of the props, our component is not going to complain:
This won’t do - we want TypeScript to yell at us if we pass both props!
To achieve that, we can create a
using two types that reflect the two scenarios our component supports:
Now if we try to provide both props, we will see a TypeScript error:
Much better! One interesting thing to note here is our usage of the never keyword. TypeScript docs explain this keyword the best:
“TypeScript will use a never type to represent a state which shouldn’t exist.”
To reiterate, we defined two types for two scenarios and combined them using the union operator.
Let’s step up from our previous use case and build a component that has different variants.
Let’s say we’re building a universal loader. There are three different variants our loader could have: spinner, text, or progress bar.
For each variant, we have a set of props. We want those props to be provided only when the client selected a matching variant.
What we need to do is define a union type that covers all three variants and has a shared
We’re using the
variant prop to tell the client which additional properties to provide. If you set the variant to
progressBar, you will see an error:
Once we set the
variant prop to
progressBar, TypeScript narrows down component’s type to progressbar and tells you that you need to provide the
On top of that, if you try to provide the
radius property, TypeScript will complain that the property is not a part of the progress bar’s type. Pretty neat!
This technique is based on the discriminated union. It helps us narrow out the members of that union using common property. In our case, the
variant was our common property. You can read more about discriminated unions
Conditional props for collection items
For our next use case, let’s try defining conditional props for a Select component. Our component needs to be flexible enough to accept an array of strings or objects for its
If the component receives an array of objects, we want the client to specify which fields of those objects we should use as a label and value.
Here’s how we would set up the type for our component using discriminated unions and
Autocomplete with generics
We can improve on our Select by adding an autocomplete feature to our
valueProp. To do so, we can use
Let’s add generics to our Select’s type like so:
In our second type, we change the
options prop from
Array<T> for our generic object. The client has to provide an array of items of the generic object type.
We’re using the
keyword to let TypeScript know that we expect
valueProp to be fields of the generic object.
Now when you try to provide
labelProp, you’ll see a nice autocomplete suggestion based on the fields of the options items.
On top of that, if you provide a value that is not a field of the options items, you will see an error:
So far so good!
There is, however, a slight modification that we need to add to prevent certain bugs. We want to make sure that the generic object provided to us is a custom object and not a primitive, like a string:
Here we’re using a ternary operator to check if our generic type is a string, and based on that, we set our component’s type to the appropriate option.
Now our component’s type definition is finished and ready to be shipped!
In this post, we went over some techniques for building intelligent components APIs using TypeScript. Using these techniques, you can write components with clear and explicit prop requirements. It’s also a powerful tool for building component libraries.
If you would like to learn more techniques for writing clean and declarative components check out my post about compound component pattern for React.
Here’s a link to the code sandbox for this tutorial.