Switch

Pattern matching and multi-way conditional rendering for declarative multi-condition logic.

The Switch component provides a declarative pattern-matching approach for rendering content based on multiple conditions, similar to switch statements in JavaScript.

Installation

npm install @zayne-labs/ui-react

Preview

Loading...

Usage

import { Switch } from "@zayne-labs/ui-react/common/switch";

function StatusIndicator({ status }) {
	return (
		<Switch.Root value={status}>
			<Switch.Match when="loading">Loading...</Switch.Match>
			<Switch.Match when="success">Success!</Switch.Match>
			<Switch.Match when="error">Error occurred</Switch.Match>
			<Switch.Default>Unknown status</Switch.Default>
		</Switch.Root>
	);
}

Component Source

"use client";import { getRegularChildren, getSingleSlot } from "@zayne-labs/toolkit-react/utils";import { isFunction } from "@zayne-labs/toolkit-type-helpers";type ValidSwitchComponentType = React.ReactElement<SwitchMatchProps<unknown>>;export type SwitchRootProps<TValue> = {	children: ValidSwitchComponentType | ValidSwitchComponentType[];	value?: TValue;};const defaultValueSymbol = Symbol("default-value");// TODO - Add a factory to make this 'when' and 'value' type-safe later following the link below// LINK - https://tkdodo.eu/blog/building-type-safe-compound-components#component-factory-patternexport function SwitchRoot<TValue>(props: SwitchRootProps<TValue>) {	const { children, value = defaultValueSymbol } = props;	const defaultCase = getSingleSlot(children, SwitchDefault, {		errorMessage: "Only one <Switch.Default> component is allowed",		throwOnMultipleSlotMatch: true,	});	const childrenCasesArray = getRegularChildren(children, SwitchDefault) as ValidSwitchComponentType[];	const matchedCase = childrenCasesArray.find((child) => {		// == If value is defaultValueSymbol, match the cases in order like switch(true)		if (value === defaultValueSymbol) {			return Boolean(child.props.when);		}		// == Otherwise, match the cases like switch(value)		return child.props.when === value;	});	return matchedCase ?? defaultCase;}export type SwitchMatchProps<TWhen> = {	children: React.ReactNode | ((value: TWhen) => React.ReactNode);	when: false | TWhen | null | undefined;};export function SwitchMatch<TWhen>(props: SwitchMatchProps<TWhen>) {	const { children, when } = props;	const resolvedChildren = isFunction(children) ? children(when as TWhen) : children;	return resolvedChildren;}export function SwitchDefault({ children }: { children: React.ReactNode }) {	return children;}SwitchDefault.slotSymbol = Symbol("switch-default");

Component API

Examples

Value Matching

In value matching mode, you provide a value to Switch.Root, and each Switch.Match checks its when prop against that value.

<Switch.Root value={status}>
	<Switch.Match when="loading">Loading...</Switch.Match>
	<Switch.Match when="success">Success!</Switch.Match>
	<Switch.Match when="error">Error occurred</Switch.Match>
	<Switch.Default>Unknown status</Switch.Default>
</Switch.Root>

Boolean Matching

Omit the value prop to use boolean matching. It acts like a switch(true) block, rendering the first component whose when condition evaluates to truthy.

<Switch.Root>
	<Switch.Match when={isLoading}>Loading...</Switch.Match>
	<Switch.Match when={error}>Error: {error.message}</Switch.Match>
	<Switch.Match when={user}>{(data) => <div>Welcome, {data.name}!</div>}</Switch.Match>
	<Switch.Default>Please log in</Switch.Default>
</Switch.Root>

Complex Layout Switching

<Switch.Root value={viewMode}>
	<Switch.Match when="grid">
		<div className="grid grid-cols-3 gap-4">{/* ... */}</div>
	</Switch.Match>

	<Switch.Match when="list">
		<div className="flex flex-col gap-2">{/* ... */}</div>
	</Switch.Match>

	<Switch.Default>
		<p>Select a view mode.</p>
	</Switch.Default>
</Switch.Root>

Only the first matching Switch.Match is rendered. This prevents the "multiple truth" problem common when stacking ternary operators.

Edit on GitHub

Last updated on

On this page