There are plenty of articles showing the typical
?: ternary approaches along with the usual warnings or considerations when using them. For example, it’s common knowledge that many nested ternaries can harm code readability, so often a
switch might be used in it’s place, or multiple components are rendered where each one conditionally renders itself. This topic is surprisingly a common source of heated debate in the React community.
switch in the context of React is an improvement, unfortunately, a
switch statement is not exhaustive like pattern matching in a functional language. This means it’s easy to forget to update a
This pattern isn’t specific to just React. I considered demonstrating the pattern for multiple frameworks including Vue but decided against it.
There is an alternative approach that leads to more readable code while also adding extra type safety. I hesitate to call this a “new” or less known conditional rendering method, but after implementing it in a project and then searching for it online I have not seen this pattern described in this specific way using TypeScript.
For no specific reason (besides fruit being delicious), I decided to have a component that renders a fact about the chosen fruit. Conditionally rendering based on several states is common for status labels, forms, stepper components, wizards, and the like. To reiterate, there is no
if/else if chain,
switch, or nested ternaries in the below component!
Instead, a more maintainable and compact solution is used. It is essentially a lookup table using an object - but with added type-safety, which allows TypeScript to ensure all states are included.
To begin with, I created a union type that contains all possible states. The union isn’t an absolute necessity for this general pattern, but it’s how I can enforce type-safety and exhaustive pattern matching. A union, as those familiar with TypeScript will know, can be a union of disparate types - not just strings. For this particular example, strings work well. They will be passed as props to the React component.
We now have TypeScript feedback during development.
Next, I created a lookup table object. This object is a map of the union type to a string containing the fruit facts. The not type-safe way you often see in typical code would be a plain object where the key is a string, and the value is a string. As I’ve already explained, we would not get the TypeScript feedback, and type safety demonstrated previously.
Record<Keys, Type>- Constructs an object type whose property keys are Keys and whose property values are Type. This utility can be used to map the properties of a type to another type.
The next step is to apply the same pattern to the conditional rendering of elements or components. For this, instead of a record mapping the fruit union to a string, we will map it to a React node. In our case, we have a specific component for each fruit emoji.
It’s worth reiterating what we have so far because this is a powerful pattern:
- A union type of all possible states
- Record types mapping from the state to strings and React nodes
- Exhaustive pattern matching, which ensures all states have to be included to be valid code
The entire component is below. The correct icon and fact will be rendered depending on which fruit is passed as a prop. This component demonstrates how to conditionally render strings and elements based on a type-safe lookup table. TypeScript will ensure all states are included, and the correct type is returned. The pattern significantly improves code readability and maintainability.
It can be taken further, too, where the union type contains objects itself instead of strings. The possibilities are numerous. This is not going to be how all conditional rendering is done, but it is a very nice pattern. A traditional
switch will not tell you if you’ve forgotten to include a state, but this pattern will.
Usage and testing
Using and unit testing the component is just like any other React component. This example unit test uses react-testing-library. Having a finite set of possible states means we can use a parameterised test to test all possible input states.
This pattern is a way to make it type-safe and ensure all states are covered. I hope you found it useful. I have written some other articles on React and TypeScript if you would like to read more. I have a newsletter too!