A conditional rendering Component for React

Giancarlo Buomprisco
·
·4 min read

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 Javascript
  • Condition 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, then children will be displayed
  • fallback: it's optional and will be displayed if it's defined, and if it's the condition is falsy

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 the children. 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 the fallback property is defined, we render fallback
  • 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>
  );
}
CodeSandbox Demo

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!