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
- 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
- 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;
}
- 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>
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.
Mirror of this article on my personal site.
Top comments (0)