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
union type
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.
Type Variants
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 variant
property:
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 toprogressBar
, TypeScript narrows down component’s type to progressbar and tells you that you need to provide the progress
property.
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
here
.
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 options
property.
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 never
:
Autocomplete with generics
We can improve on our Select by adding an autocomplete feature to our labelProp
and valueProp
. To do so, we can use
generics
in TypeScript.
Let’s add generics to our Select’s type like so:
In our second type, we change the options
prop from Array<Object>
to Array<T>
for our generic object. The client has to provide an array of items of the generic object type.
We’re using the
keyof
keyword to let TypeScript know that we expect labelProp
and valueProp
to be fields of the generic object.
Now when you try to provide valueProp
or 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.
If you’d like to get more web development, React and TypeScript tips consider
following me on Twitter,
where I share things as I learn them.
Happy coding!