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.
function Show(props) {
return props.if ? props.children : null;
};
export default Show;
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
function Show(props) {
return props.if ? props.children : null;
};
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
The Issue
Try clicking the above Person Not Found
button which sets the state to undefined and you'll get the below error
😮 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:-
{ (person && person.online) &&
<h2>{person.name} is online</h2>
}
<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.
export default ({ if: show, children }) =>
show ? (typeof children === 'function' ? children() : children) : null;
// ^-----------------------------------------^
<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.
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.
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.
Observations
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.
👍 our conditional renderer
Show
component only re-renders when we toggle theonline
state which is relevant to ourShow
component not when thecount
state changes.
Here is our final solution
export default React.memo(({ if: show, children }) =>
show ? (typeof children === 'function' ? children() : children) : null
);
<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.