DEV Community

Michael Musatov
Michael Musatov

Posted on • Edited on

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Validate boilerplate by running dev server with the following command:

yarn dev
Enter fullscreen mode Exit fullscreen mode

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

  • Remove unnecessary code from App.tsx component to get it to the following state:
import './App.css'

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

export default App
Enter fullscreen mode Exit fullscreen mode
  • Remove most of the styles for App.css and keep only the following:
#root {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
}
Enter fullscreen mode Exit fullscreen mode
  • Beside that the following files can be removed:
    • src/assets/react.svg
    • public/vite.svg

The never type is used here. It represents the value which will never occur. The full info in the specification https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#the-never-type

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>
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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}</>
}
Enter fullscreen mode Exit fullscreen mode

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}</>
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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];
Enter fullscreen mode Exit fullscreen mode

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}</>;
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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

const [condition, setCondition] = useState<undefined | string>()
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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.

Mirror of this article on my personal site.

Top comments (0)