A conditional rendering Component for React
This post shows how to create a React component to render components conditionally.
JSX allows a lot of freedom for what you can do in your components: at the end of the day, it's just Javascript.
If you're like me and you've primarily worked with template-based frameworks (such as Vue or Angular), you may be used to directives or components that take care of the flow: conditions, for-loops, switch, etc.
React doesn't have these baked in (like SolidJS), but we can easily add it.
This blog post will show you the conditional rendering component, which you can use with React, but it is also easily applicable to any other JSX-based library.
One challenge I faced was making sure the result of the condition was correctly cast so that we could safely use it with Typescript.
Let's write the If component
First, we define two type aliases with Typescript:
Falsy
which represent values that are falsy in JavascriptCondition
which is the value passed to the component
type Falsy = undefined | null | 0 | "";
type Condition<Value = unknown> = Value | Falsy;
We're going to call the component If
.
It accepts two properties:
condition
: it's any value. If it is truthy, thenchildren
will be displayedfallback
: it's optional and will be displayed if it's defined, and if it's the condition isfalsy
Of course, it also accepts a children
property, which we augment to take a function.
Why? Because we want to pass the "casted" value to the children's tree.
For example, if I pass a component Button
as a condition, I want to be able to use Button
within the tree because it is not falsy
.
To do so, Button
will be passed down casted as a parameter of the function we define in the children
.
type Props = React.PropsWithChildren<{
condition: Condition<Value>;
fallback?: ReactNode;
children: ReactNode | ((value: Value) => ReactNode);
}>
function If<Value = unknown>({
condition,
fallback,
children,
}: Props) {
// body
}
We can now write the body:
- if
condition
is truthy, we can render thechildren
. If it's a function, then we call it and passed the result of the condition down as a parameter - if
condition
is falsy, and thefallback
property is defined, we renderfallback
- otherwise, we return
null
so that nothing gets rendered
return useMemo(() => {
if (condition) {
if (typeof children === "function") {
return <>{children(condition)}</>;
}
return <>{children}</>;
}
if (fallback) {
return <>{fallback}</>;
}
return null;
}, [condition, fallback, children]);
And here is the complete snippet:
type Falsy = undefined | null | 0 | "";
type Condition<Value = unknown> = Value | Falsy;
function If<Value = unknown>({
condition,
children,
fallback
}: React.PropsWithChildren<{
condition: Condition<Value>;
children: ReactNode | ((value: Value) => ReactNode);
fallback?: ReactNode;
}>) {
return useMemo(() => {
if (condition) {
if (typeof children === "function") {
return <>{children(condition)}</>;
}
return <>{children}</>;
}
if (fallback) {
return <>{fallback}</>;
}
return null;
}, [condition, fallback, children]);
}
Example
Let's see a quick example that shows how to use the If
component:
function App() {
const [value, setValue] = useState<number>(0);
const onChange = (e: FormEvent<HTMLInputElement>) =>
setValue(Number(e.currentTarget.value));
const fallback = <span>Fallback content</span>;
return (
<div className="App">
<div>
<input value={value} type="number" onChange={onChange} />
</div>
<If condition={value} fallback={fallback}>
Value: {value}
</If>
</div>
);
}
Conditionally rendering a component from the props
Another useful case I found is when I pass components as optional props, and conditionally render the component if the consumer has passed it.
Let's assume we have a component that accepts another one.
In the ideal world, I could write the following:
const ReusableComponent: React.FC<{
heading?: React.FC;
}> = ({ heading: Heading, children }) => {
return (
<div>
<If condition={Heading}>
<Heading />
</If>
</div>
);
};
Unfortunately, I (or Typescript) am not smart enough to make the children
tree understand that Heading
was casted as truthy.
Heading
will error-out because the type-checking cannot understand that the If
tree is only rendered when the condition value is truthy.
So we use a function to pass the casted value:
const ReusableComponent: React.FC<{
heading?: React.FC;
}> = ({ heading, children }) => {
return (
<div>
<If condition={heading}>
{(Heading) => <Heading />}
</If>
</div>
);
};
I am generally happy with the readability that this offers compared to ternaries, &&, or other weird ways that make Typescript happy.
Of course, let me know if you have any questions or suggestions to improve this!