liveBook · Manning

Union type

As explained in React in Depth

A discriminated union is a TypeScript union type whose alternatives are distinguished by a shared property with distinct literal values.

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

How React in Depth covers it

How deep it goes
Defines interfaces, unions them, narrows with literal checks, and notes that extracted rest properties lose discriminator correlation.
Examples
Uses an e-commerce product card with regular and on-sale variants, sale price, and expiry date.
What it assumes
Assumes React component props and TypeScript interfaces, then demonstrates the pattern through a product-card component.
What you can do after
Build it from scratch by modeling React component configurations and safely accessing variant-specific properties after narrowing.
How it connects
Connects React component props, TypeScript interfaces, intersection types, and conditional type checking.

Also explained in

Compare how 10 books explain union type →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of React in Depth. The section links point to the parts of the book it draws on.