React.js is a popular JavaScript library used for building user interfaces. With its component-based architecture, React allows developers to easily create reusable UI components. In addition, TypeScript is a type-safe superset of JavaScript that helps developers catch errors early on during development. Combining React with TypeScript can lead to even more robust and error-free code. However, in this article, we will explore how to use conditional props in React.js with the help of TypeScript's discrimination union types.
What are Discrimination Union Types?
Discrimination union types are a type of union type that allows you to narrow down the set of possible values of a union based on a common property. In other words, discrimination union types allow you to define a type that depends on the value of a property. This feature is particularly useful when working with React components that have conditional props.
Example
Let's say we have a component called Input that can have two types of props: type="text"
and type="number"
. We can define a union type that represents these two types of props as follows:
type InputProps = { type: 'text'; value: string } | { type: 'number'; value: number };
This defines a type called InputProps
that is a union of two types: one with a type property that is a string with a value of text
and a value property that is a string, and another with a type property that is a string with a value of number
and a value property that is a number. We can use this type to define our Input component like this:
function Input(props: InputProps) {
return (
<input type={props.type} value={props.value} />
);
}
Now, when we use this component, we can pass in either a type="text"
or type="number"
prop, and the value
prop will be typed accordingly:
<Input type="text" value="Hello World" /> // Renders a text input with "Hello World" value
<Input type="number" value={42} /> // Renders a number input with "42" value
<Input type="text" value={42} /> // Error: 'value' must be a string for type='text'
<Input type="number" value="Hello World" // Error: 'value' must be a number for type='number'
Another Example
Let's say we have a component called PaymentMethod
that can have two types of props: method="credit-card"
and method="paypal"
. We can define a union type that represents these two types of props as follows:
type PaymentMethodProps =
| { method: 'credit-card'; cardNumber: string; cvv: string }
| { method: 'paypal'; email: string };
This defines a type called PaymentMethodProps
that is a union of two types: one with a method
property that is a string with a value of 'credit-card'
, a cardNumber
property that is a string, and a cvv
property that is a string. The other type has a method
property that is a string with a value of 'paypal'
and an email
property that is a string. We can use this type to define our PaymentMethod
component like this:
function PaymentMethod(props: PaymentMethodProps) {
return (
<div>
{props.method === 'credit-card' && (
<>
<label>Card Number:</label>
<input type="text" value={props.cardNumber} />
<label>CVV:</label>
<input type="text" value={props.cvv} />
</>
)}
{props.method === 'paypal' && (
<>
<label>Email:</label>
<input type="email" value={props.email} />
</>
)}
</div>
);
}
Now, when we use this component, we can pass in either a method="credit-card"
or method="paypal"
prop, but not both. Additionally, if method="credit-card"
, then cardNumber
and cvv
props must be present, and if method="paypal"
, then email
prop must be present:
<PaymentMethod method="credit-card" cardNumber="1234567890123456" cvv="123" /> // Renders a credit card input form
<PaymentMethod method="paypal" email="example@example.com" /> // Renders a PayPal input form
<PaymentMethod method="credit-card" /> // Error: 'cardNumber' and 'cvv' must be present for method='credit-card'
<PaymentMethod method="paypal" cardNumber="1234567890123456" cvv="123" /> // Error: 'email' must be present for method='paypal'
<PaymentMethod method="credit-card" cardNumber="1234567890123456" cvv="123" email="example@example.com" /> // Error: Only one of 'cardNumber' and 'cvv', or 'email' is allowed
In this example, we defined a union type that represents two types of props that can be passed to the PaymentMethod
component. We then used conditional rendering to show either the credit card input form or the PayPal input form based on the method
prop value. Lastly, we added constraints to ensure that some props can't be present based on another prop's value.
Top comments (2)
Thanks for sharing! I use this on a daily basis in my code but didn't know the correct name of this construction :)
Short and precise. Thanks for sharing.