Skip to content

Latest commit

 

History

History
119 lines (89 loc) · 3.06 KB

File metadata and controls

119 lines (89 loc) · 3.06 KB

Discriminated Unions for Conditional Rendering

What is a Discriminated Union?

A discriminated union is a TypeScript pattern for creating a type that can be one of several variants, each identified by a specific literal property (the “discriminator”).

It’s very useful for conditional rendering in React because it forces exhaustive checks — TypeScript will warn you if you forget a case.


Basic TypeScript Example

type LoadingState = { status: 'loading' };
type SuccessState = { status: 'success'; data: string[] };
type ErrorState = { status: 'error'; message: string };

type FetchState = LoadingState | SuccessState | ErrorState;

Here, the status property is the discriminator.


Using in React for Conditional Rendering

type LoadingState = { status: 'loading' };
type SuccessState = { status: 'success'; data: string[] };
type ErrorState = { status: 'error'; message: string };

type FetchState = LoadingState | SuccessState | ErrorState;

const DataComponent: React.FC<{ state: FetchState }> = ({ state }) => {
  switch (state.status) {
    case 'loading':
      return <p>Loading...</p>;
    case 'success':
      return (
        <ul>
          {state.data.map((item, i) => (
            <li key={i}>{item}</li>
          ))}
        </ul>
      );
    case 'error':
      return <p>Error: {state.message}</p>;
    default:
      // This will trigger a TypeScript error if a case is missing
      const _exhaustiveCheck: never = state;
      return _exhaustiveCheck;
  }
};

Why It’s Powerful in React Interviews

  • Type Safety — No accidentally accessing data when in "loading" state.
  • Exhaustiveness Checking — TypeScript errors if you miss a case.
  • Clear State Management — Each variant is explicit.

Common Real-World Uses in React

  • API states: "idle" | "loading" | "success" | "error"
  • Component modes: "view" | "edit" | "create"
  • Form states: "valid" | "invalid" | "submitting"

Example with API Hook

type ApiState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; message: string };

function useApi<T>(url: string): ApiState<T> {
  const [state, setState] = React.useState<ApiState<T>>({ status: 'idle' });

  React.useEffect(() => {
    setState({ status: 'loading' });
    fetch(url)
      .then((res) => res.json())
      .then((data) => setState({ status: 'success', data }))
      .catch((err) => setState({ status: 'error', message: err.message }));
  }, [url]);

  return state;
}

// And in a component:
const state = useApi<User[]>('/api/users');

switch (state.status) {
  case 'idle':
    return <p>Waiting...</p>;
  case 'loading':
    return <p>Loading...</p>;
  case 'success':
    return <UserList users={state.data} />;
  case 'error':
    return <p>{state.message}</p>;
}

Key Interview Points

  • Discriminated unions pair a literal type with unique properties.
  • Helps in safe conditional rendering.
  • Forces you to handle all possible states at compile time.
  • Common with API data fetching patterns.