This page looks best with JavaScript enabled

React: Smart component API with conditional props and TypeScript

 ·  ☕ 5 min read  ·  ✍️ Iskander Samatov

conditional props

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:

avatar first
first Avatar iteration

Right now, if we pass both of the props, our component is not going to complain:

Avatar client sample
Avatar client sample

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:

union types avatar
union types for Avatar

Now if we try to provide both props, we will see a TypeScript error:

avatar error
Avatar 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:

variant definition
Loader variant definition

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:

Loader progress bar error
Loader progress bar 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!

Loader invalid prop error
Loader invalid prop error

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:

conditional props for collection
Conditional types for collection property

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:

Adding generics to Select types
Adding generics to Select types

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.

Autocomplete select
Autocomplete of Select

On top of that, if you provide a value that is not a field of the options items, you will see an error:

Autocomplete select error
Autocomplete Select 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:

generics ternary
Checking generics with a ternary operator

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!

Share on

Software Development Tutorials
Iskander Samatov
The best up-to-date tutorials on React, JavaScript and web development.