Skip to content

Conditional Rendering in React with a Switch Component

Posted on:September 9, 2023 at 01:12 PM

Conditional Rendering in React with a Switch Component

Controlling the rendering of UI elements based on certain conditions is a common task in web development. There are plenty of methods to accomplish this in React, and in this article, I’ll show you how to create a simple component that does it in a declarative way.

Let’s begin by setting up a React project. In this guide, I’ll be using Vite, but it’s important to note that for the purpose of this article, all the options, such as CRA (Create React App), Next.js, Remix, and more, are equally good.

Creating boilerplate project

With Vite it is pretty straightforward. Lets create it with the following command:

yarn create vite

Then just follow the master instructions. The only note, I’d like to use Typescript here.

After creating a project (I’ve named it react-switch) the dependencies need to be installed with

cd ../react-switch
yarn

Validate boilerplate by running dev server with the following command:

yarn dev

and opening the link in the browser http://127.0.0.1:5173/. Simple and beauty Vite + React boilerplate will be shown.

And then cleanup unnecessary code and resources from the project

import "./App.css";

function App() {
  return (
    <>
      <h3>React Switch Component</h3>
    </>
  );
}

export default App;
#root {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
}

The never type is used here. It represents the value which will never occur. The full info in the specification.

Also we will add empty Switch.tsx file for our future Switch component in the src directory.

Switch component implementation

The component should have the structure like below when user.

<Switch condition={<condition>}>
  <Switch.Case when={<possible-value>}>
  ...
  </Switch.Case>
  <Switch.Default>
  ...
  </Switch.Default>
</Switch>

We’re importing functional component (FC), ReactElement, and ReactNode from ‘react’. These types are foundational to the Switch component, ensuring type safety and clarity in its usage.

import { FC, ReactElement, ReactNode } from "react";

Define the Case and Default Props. CaseProps and DefaultProps interfaces for the Case and Default elements respectively. CaseProps takes children and a when prop, which can be a string or a number (which is always possible to adjust to your specific case). DefaultProps, on the other hand, only accepts children - the when prop is set to never, ensuring it’s not used with the Default component.

interface CaseProps {
  children?: ReactNode
  when: string | number
}

interface DefaultProps {
  children?: ReactNode
  when?: never
}

We need one more interface which outlines the props for the main Switch component. It should accept a condition prop, which determines which Case component to render, and the children prop which could be a single or an array of ReactElement.

interface SwitchComponentType extends FC<SwitchComponentProps> {
  Case: FC<CaseProps>
  Default: FC<DefaultProps>
}

We extend the FC type to declare the Switch component along with its nested Case and Default components. This architecture simplifies the usage syntax, making it more intuitive.

Implementation of Case and Default elements is straightforward. The main goal is to provide a wrapper for components which will be possible rendered.

Switch.Case = ({ children }) => {
  return <>{children}</>;
};

Switch.Default = ({ children }) => {
  return <>{children}</>;
};

Now lets craft the Switch component itself. This is the place where the magic happens. It processes the children props to find the matching Case component based on the condition prop or returns the Default component if no match is found.

export const Switch: SwitchComponentType = ({ condition, children }) => {
  if (!children) {
    return null;
  }

  const arrayOfChildren = Array.isArray(children) ? children : [children];
  const cases = arrayOfChildren.filter(child => child.props.when == condition);
  const defaultCases = arrayOfChildren.filter(child => !child.props.when);

  if (defaultCases.length > 1) {
    throw new Error("Only one <Switch.Default> is allowed");
  }

  const defaultCase = defaultCases[0];

  return cases.length > 0 ? <>{cases}</> : <>{defaultCase}</>;
};

Lets break it down.

Basically as a first step we check if there are any children in our Switch component. It should not render anything if children are absent.

if (!children) {
  return null;
}

Next line creates an arrayOfChildren. It checks if the children prop is an array. If it’s an array, arrayOfChildren is set to children. If it’s not an array (i.e., there’s only a single child), it wraps that child in an array, making it easier to work with later in the code.

const arrayOfChildren = Array.isArray(children) ? children : [children];

As a next step we create cases array that will contain child components whose when prop matches the provided condition. The filter method is used to iterate through each child component and select only those where the when prop is equal to the provided condition.

const cases = arrayOfChildren.filter(child => child.props.when == condition);

We also need to create defaultCases array which contains child components with no when prop defined. In this filter function, it selects child components where the when prop is falsy or undefined.

Then do the check ensures that there is at most one <Switch.Default> component. If there are multiple default cases (i.e., multiple components without a when prop), it throws an error to indicate that only one <Switch.Default> component is allowed within a Switch.

And assign defaultCase with either undefined or first and only element of defaultCases array.

const defaultCases = arrayOfChildren.filter(child => !child.props.when);
if (defaultCases.length > 1) {
  throw new Error("Only one <Switch.Default> is allowed");
}
const defaultCase = defaultCases[0];

Finally, the last piece of code (provided below) determines what to render based on the cases and defaultCase. If there are matching cases (i.e., cases.length > 0), it returns the components. If there are no matching cases, it returns the defaultCase component, or null if there’s no defaultCase.

return cases.length > 0 ? <>{cases}</> : <>{defaultCase}</>;

Switch component usage

We are suing Switch component inside App component. The full code of updated App component is below.

import { useState } from 'react'
import './App.css'
import { Switch } from './Switch'

function App() {
  const [condition, setCondition] = useState<undefined | string>()

  return (
    <>
      <h3>React Switch Component</h3>

      <div className='field-container'>
        <label className='label' htmlFor="select-input">Possible values:</label>
        <select id="select-input" onChange={(e) => setCondition(e.target?.value)}>
          <option>Not defined</option>
          <option value="1">1</option>
          <option value="2">2</option>
        </select>
      </div>

      <div className='switch-section-container'>
        <div className='label'>Possible values:</div>
        <Switch condition={condition}>
          <Switch.Case when={1}>
            <div className="case-item">1</div>
          </Switch.Case>
          <Switch.Case when={2}>
            <div className="case-item">2</div>
          </Switch.Case>
          <Switch.Default>
            <div className="case-item">Default</div>
          </Switch.Default>
        </Switch>
      </div>
    </>
  )
}

export default App

Lets review the changes line by line.

To demonstrate a usage and test our Switch component lets import it to the App.tsx

import { Switch } from "./Switch";

Also to change and check conditions we should add some state to the App component

const [condition, setCondition] = useState<undefined | string>()

This state is modified by a select element.

<select id="select-input" onChange={e => setCondition(e.target?.value)}>
  <option>Not defined</option>
  <option value="1">1</option>
  <option value="2">2</option>
</select>

And used to conditionally display different cases content with our newly created Switch component.

<Switch condition={condition}>
  <Switch.Case when={1}>
    <div className="case-item">1</div>
  </Switch.Case>
  <Switch.Case when={2}>
    <div className="case-item">2</div>
  </Switch.Case>
  <Switch.Default>
    <div className="case-item">Default</div>
  </Switch.Default>
</Switch>

Conclusion

In summary, the Switch component we’ve created allows a cleaner and more expressive way to handle conditional rendering in React, similar to a switch-case statement. It filters and renders child components based on the provided condition, providing a more intuitive and readable approach to conditional rendering in React applications. While it can be used as-is, it primarily serves as an example of creating conditional rendering components in React.

The complete source code is accessible on GitHub at this link, and it can be explored interactively using StackBlitz at this link.