A discriminated union models a value as one of several object shapes, with a shared property whose distinct literal values identify the active alternative. In React props, this prevents invalid combinations while allowing TypeScript to narrow the type after checking the discriminator. section 5.3.7 section 5.4
Build the pattern by defining each valid configuration explicitly:
- Give each alternative the same discriminator, such as isOnSale: true or isOnSale: false. section 5.3.7
- Put configuration-specific properties only on the alternative that requires them. section 5.3.7
- Union the alternatives so the component accepts either complete shape, but not a mixture. section 5.3.7
- Check the discriminator before reading variant-specific properties; TypeScript then narrows the object to the matching interface. section 5.3.7
- Extract shared properties into a base interface and intersect it with the discriminated union when duplication becomes noticeable. section 5.3.7
Product-card props
interface ProductCardSaleProps { productName: string; price: number; isOnSale: true; salePrice: number; saleExpiry: string; }\ninterface ProductCardNoSaleProps { productName: string; price: number; isOnSale: false; }\ntype ProductCardProps = ProductCardSaleProps | ProductCardNoSaleProps;\n\nfunction ProductCard(props: ProductCardProps) {\n if (props.isOnSale) {\n return <div>{props.salePrice} — expires {props.saleExpiry}</div>;\n }\n return <div>{props.price}</div>;\n} section 5.3.7
Listing 5.10 Product card with a discriminated union
import "./ProductCard.css";
interface ProductCardSaleProps { #1
productName: string;
price: number;
isOnSale: true;
salePrice: number;
saleExpiry: string;
}
interface ProductCardNoSaleProps { #2
productName: string;
price: number;
isOnSale: false;
}
type ProductCardProps = #3
| ProductCardSaleProps #3
| ProductCardNoSaleProps; #3
function formatPrice(price: number) { #4
return price.toLocaleString("en-US", {
style: "currency",
currency: "USD",
});
}
export function ProductCard( #5
props: ProductCardProps #5
) { #5
const { productName, price, isOnSale } = props; #6
return (
<div className={`product-card ${isOnSale ? "on-sale" : ""}`}>
<h3 className="product-name">{productName}</h3>
<div className="price">
<div className="regular-price">{formatPrice(price)}</div>
{isOnSale && ( #7
<>
<div className="sale-price">
{formatPrice(props.salePrice)} #8
</div>
<div className="sale-expiry">
Sale ends: {props.saleExpiry} #8
</div>
</>
)}
</div>
</div>
);
}
Browse the complete product-card implementation, including literal discriminators, narrowing, price formatting, and rendered sale details. section 5.3.7









