Skip to main content

React Conditional Rendering - can custom component replace inline conditional JSX

0%

Can we replace inline conditional JSX with our own React Component ! Let's learn few concepts while I walk you through about what I've found while working on an idea to improve the redability of conditional inline rendering and it totally failed.. but taught me a lot.

Table of Contents
 

React allows declarative design of the view that means you can describe the desired UI by telling react when and what to render upon conditions. Conditional rendering in React is quite a popular topic and the more dynamic and interactive the UI becomes you'll find the need for conditional appearance (render) of certain elements. There are any number of ways we can tell React to conditionally render elements or components based on certain condition / state / prop change but I'm not going to go over all those options, I'm sure you'll find countless number of articles about that, even the official guide is great.

It all starts with a tweet

I follow a great number of wonderful open-source contributors and instructors in twitter and it happens a week ago someone posted a code screenshot which I'm not sure about what language it was but it looked kind of XMLish where a chunk of code were wrapped by an <Condition> tag.

What if the same can be done in React! it would be a handy solution to the mess that is created while we inline conditions in JSX by expanding the braces, although at the same time I thought such a simple idea how no one is talking about it ! 🤔.

Let us start building our own component

Like the twitter code-shot why not we start building our own component.

💡 What if we can use the component as:

<Show if={condition}>content here<Show>
Easy !

Seems like tooo simple to build the component, it should have a prop named if which accepts boolean and a children prop which will be rendered if condition is true else return null.

Show.js
function Show(props) {
return props.if ? props.children : null;
};

export default Show;
Live Editor
Result
Loading...

Clean it up a little bit and learn few topics before we move further

Step 1: the name of the functional component doesn't matter here, the name we use while importing the component becomes the component name so let's get rid of it, also the export can be made inline here as a default exportable component

export default function(props) {
return props.if ? props.children : null;
};

Step 2: why not destructure the props inline

export default function({if, children}) {
return if ? children : null;
};
note

here we'll get an error - Unexpected keyword 'if'

like in most other programming language if is a reserved keyword so we need to destructure by aliasing the prop name to something else

                            👇
export default function({if: show, children}) { // aliasing `if` to `show`
return show? children : null;
};

Step 3: we can shorten the component further by converting to an arrow function

 export default ({if: show, children}) => show? children : null;

Step 4: this is a very good use case for Short-circuit AND operator (&&) as a replacement of the ternary statement.

(true && expression always evaluates to expression)

export default ({if: show, children}) => show && children;
 

Looks like we made a decent job

Before
function Show(props) {
return props.if ? props.children : null;
};
After 👍
export default ({if: show, children}) => show && children;


Play with the component a bit more

The example below try to render the person's name when the state is online | offline | undefined

Live Editor
Result
Loading...

The Issue

Try clicking the above Person Not Found button which sets the state to undefined and you'll get the below error

Cannot read properties of undefined (reading 'name')

😮 Strange ! we are checking if the person is present and whether he is online and still the component children got executed?

Well the fact is this is how React works. A React component always evaluates all of its properties including the body.

key note

That essentially means React will evaluate the body of our Show component and then pass it as children prop to it

This is a big problem, if anyway the Show wrapped content gets evaluated then this is a serious performance issue, the conditional checks are therefore totally useless.

 

⚡ So it is clearly visible that these two are not functioning the same way:-

Inline conditional JSX
{ (person && person.online) && 
<h2>{person.name} is online</h2>
}
React Component
<Show if={person && person.online}>
<h2>{person.name} is online</h2>
</Show>

The workaround

JavaScript is an eagerly evaluated language that's why children of our Show component gets evaluated regardless of the value of the condition. Sounds like if we can somehow make the evaluation of the code chunk lazy then we might be able to make it work as we want.

To do so we need to wrap the children in a function and force React into lazy evaluation, also our custom Show component needs to be able to handle children as function too.

Show.js - rewrite to accept children as function
export default ({ if: show, children }) =>
show ? (typeof children === 'function' ? children() : children) : null;
// ^-----------------------------------------^
Usage
<Show if={person && person.online}>
{() => `Hi ${person.name}`}
</Show>

by now you may already have guessed our experiment is not going to be fruitful as the complexity this approach already introduces, but let's not give up already we'll dig little bit further.

What about the performance now!

An easy way to find if a component is re-rendering is to throw a console.log anywhere in the function body.

Show.js
export default ({ if: show, children }) => {
console.log('re-rendering Show Component');

return show ? (typeof children === "function" ? children() : children) : null;
}
to execute multiple statements temporarily making the one-liner function to multi-liner

Before testing let's also add a performance booster called React.memo - if a component renders the same result given the same props memo can prevent re-renders.

Show.js
export default React.memo(({ if: show, children }) => {
console.log('re-rendering Show Component');

return show ? (typeof children === "function" ? children() : children) : null;
});
 

Let's try this out

Adding a local state count for our main App component which has no effect on how we conditionally render anything and we have added a console.log to our Show component to findout how often the component gets re-rendered.

Live Editor
Result
Loading...

Observations

Clicking any of the button in the above example triggers re-render of `Show` component.

The desired outcome is each time we toggle the online status Show component should re-render but when changing the count state it should not re-render. React component will re-render itself if any state / prop changes and causing any children component to re-render but here we have optimized our component using memo, still why it re-renders ?

Here is why - when prop change happens React compares the last prop with the current to check any difference and if the component is wrapped with memo then it skips the rerender when last & current props are same, but in our case we are passing a function as children prop so React is having hard time to compare if last function is identical to current.

The Fix

For particular to this type of situations when we need to pass a function as prop, React provides another wrapper called useCallback read more in detail here. useCallback accepts an inline callback and an array of dependencies.

Live Editor
Result
Loading...

👍 our conditional renderer Show component only re-renders when we toggle the online state which is relevant to our Show component not when the count state changes.

Here is our final solution

Show.js
export default React.memo(({ if: show, children }) =>
show ? (typeof children === 'function' ? children() : children) : null
);
Usage
<Show if={person && person.online}>
{React.useCallback(
() => (
<h5>🟢 {person.name} is online</h5>
),
[person]
)}
</Show>

Signing off 😇

Well, there you have it! we have done our experiments to find out whether a custom React component can replace the conditional inline JSX, but it turns out that we are back to the { braces } and everything needs to be wrapped with useCallback, it is just too much of a hassle, but luckily it opened up the scopes for us to interestingly explore few useful topics like aliasing destructure, unnamed default export, useCallback etc.

Thanks for following along. Whether you're a developer experienced in the framework or one who's a newcomer to React, I hope you picked up some useful tips here and hopefully found the article interesting.