What is conditional rendering?
As we continue developing small and simple programs in React, we'll eventually land in a position where we wish to conditionally render stuff.
For example, we might want to display a question's correct answer when a given option is selected. Or we might want to show a loading indicator if a given piece of data hasn't loaded yet. Or, as another example, we might want to make a password field visible when the eye toggle is selected. And so on.
This manner of rendering stuff in React based on the outcome of a given condition is commonly referred to as conditional rendering.
In React, in particular in JSX, there are many conventions surrounding conditional rendering.
In the following sections, we take a look at these conventions but before that, let's take a quick look at the naive way of dealing with conditions in JavaScript, and thus in React as well — using if
and else
.
Conditional rendering using if
Let's consider a simple program to implement.
Suppose we have a component Greeting
meant to greet the user with an <h1>
. It has a showInfo
Boolean prop specifying whether some extra piece of information should be shown along with the <h1>
.
When we instantiate the component as follows, we expect the information to be shown since showInfo
is true
:
<Greeting name="Dennis" showInfo />
Remember that when an attribute is not given a value in JSX, like showInfo
here, it automatically gets assigned the value true
in the converted JavaScript.
Similarly, when we instantiate the component as follows, we expect the opposite — the information to be hidden:
<Greeting name="Dennis" />
Remember that when a prop is not provided to a React element, the access of that prop inside the component resolves to undefined
. Basic JavaScript.
Now, let's think about how to implement this component.
A naive approach, using JavaScript's if
statement, follows:
function Greeting({ name, showInfo }) {
if (!showInfo) {
return (
<header>
<h1>Hello {name}!</h1>
</header>
);
}
return (
<header>
<h1>Hello {name}!</h1>
<p>This is some extra information shown.</p>
</header>
);
}
The code is a little verbose but it does its job.
When showInfo
is not given (i.e. has a falsey value), the returned set of elements doesn't contain a <p>
element:
function App() {
return (
<Greeting name="Dennis" />
);
}
Otherwise, the <p>
comes after the <h1>
:
function App() {
return (
<Greeting name="Dennis" showInfo />
);
}
Now, as you'd definitely agree, this approach doesn't make much sense because in both cases, we're returning, more or less, the same kind of elements except for the <p>
. We're copy/pasting the set of elements and that's not desirable.
Imagine that we had 100 lines of JSX code for our set of elements, again with one conditional <p>
in there; would it now make sense to copy/paste the code in two places, one without the <p>
and one with it? Clearly not!
This approach of if
is mostly used when a completely different value is meant to be returned by a component.
Let's see such an example.
In the following code, we have a different Greeting
component where we don't return anything when the name
prop is not provided (or explicitly undefined
):
function Greeting({ name }) {
if (name === undefined) {
return null;
}
return (
<h1>{name}</h1>
);
}
Let's see what happens by instantiating Greeting
without a name
prop:
function App() {
return (
<Greeting/>
);
}
As can be seen in the linked page above, nothing is rendered — we get a blank page.
This is simply because name
is not provided and, consequently, Greeting
returns null
which, as we learnt in the React JSX chapter, doesn't get rendered.
Anyways, coming back to our original discussion, of the if
statement being used in a scenario with an almost similar set of returned elements in both cases (when the condition is met and when it ain't), we need a better approach here.
What we need is something that allows us to express the set of elements exactly once, with the conditional bits and pieces inlined into that set of elements. In other words, we need some way to only express the <p>
element conditionally without affecting other elements.
This basically means that we ask for an expression to represent conditional code.
And the good news is that JavaScript ships with three operators out of the box to help us in this regard: logical AND (&&
), logical OR (||
), and the conditional operator (also known as the ternary operator).
Let's see how to work with these operators to conditionally render elements in React.
The logical AND (&&
) operator
The logical AND (&&
) operator evaluates its left operand and if it's truthy, resolves down to the the value of the right operand. Otherwise, when the left operand is falsey, it resolves down to that very operand.
This operator should be used whenever we have a value that dictates the rendering of some other piece of content, however the value itself couldn't be directly rendered.
Let's see an example.
We don't have to go far away in search for an example; our example is right in the previous section. That is, let's use the logical AND (&&
) operator to conditionally render the <p>
element in the Greeting
component shown above.
Here's our code with &&
:
function Greeting({ name, showInfo }) {
return (
<header>
<h1>Hello {name}!</h1>
{showInfo && <p>This is some extra information shown.</p>}
</header>
);
}
Notice a couple of things here:
- Firstly, we've removed the
if
statement since that isn't needed. - Secondly, the conditional
<p>
element has been inlined into the JSX code, all thanks to the logical AND operator. - And lastly, the entire logical AND expression is wrapped up in curly braces (
{}
). This is simply because the JSX parser expects JSX here but we wish to denote some JavaScript code —{}
instructs the parser to treat whatever is inside it as JavaScript code.
Clearly, using logical AND (&&
) leads to a much more leaner and neater code.
The logical AND (&&
) operator should be used whenever we wish to render content based on the value of a given identifier, or else render nothing.
?:
), as we shall see later on below.Now, let's talk about the logical OR (||
) operator.
The logical OR (||
) operator
The logical OR (||
) operator evaluates the left operand and if it's truthy, resolves down to it, or else resolves to the right operand.
This operator should be employed whenever we wish to use a default value in place of a given arbitrary value in the instance when that arbitrary value doesn't fulfill a given criteria (for e.g. maybe it's undefined
).
Here's an example.
Suppose we redesign our Greeting
component to now take the extra information in the form of an info
prop, instead of the information being hardcoded into the JSX and put behind a showInfo
condition.
In the scenario when info
is not given or is falsey, we must show the default text 'This is the default info.' inside the <p>
element. Otherwise, we must show the info
itself, again inside the <p>
element.
Now because we're talking about relying on a default value here when info
is falsey, we seek the logical OR (||
) operator:
function Greeting({ name, info }) {
return (
<header>
<h1>Hello {name}!</h1>
<p>{info || 'This is the default info.'}</p>
</header>
);
}
Note that because the <p>
element is needed in both the cases, it is outside the conditional.
Let's try instantiating this component with and without info
.
First, without info
:
function App() {
return (
<Greeting name="Dennis" />
);
}
And now with info
:
function App() {
return (
<Greeting name="Dennis" info="Our own info." />
);
}
Super-duper amazing.
Why we didn't assign a default value while destructuring?
You might be wondering why we didn't assign a default value to info
in the destructuring expression. Something like this:
function Greeting({ name, info = 'This is the default info.' }) {
return (
<header>
<h1>Hello {name}!</h1>
<p>{info}</p>
</header>
);
}
Let's see why we didn't use this for our problem.
Suppose a component instantiates Greeting
and always sets info
to either a string or the value null
. There is never ever a case where the component doesn't provide the info
prop — it provided 'always'.
Now, if we had the code above, with info
assigned a default value in the destructuring construct, providing null
would set info
to null
and then this would be rendered in the <p>
element, leading to...you guessed it...nothing rendered:
function Greeting({ name, info = 'This is the default info.' }) {
return (
<header>
<h1>Hello {name}!</h1>
<p>{info}</p>
</header>
);
}
<Greeting name="Dennis" info={null} /> // Nothing rendered
But if we have the expression info || 'This is...
, providing null
to info
would resort back to the default information text in the <p>
:
function Greeting({ name, info }) {
return (
<header>
<h1>Hello {name}!</h1>
<p>{info || 'This is the default info.'}</p>
</header>
);
}
<Greeting name="Dennis" info={null} /> // 'This is the default info.' rendered
This is exactly what we'd ideally want.
Thus, to prevent the absence of information in case info
is null
, we stick to using the logical OR (||
) operator with info
directly in the <p>
element.
Obviously, using default values in the destructuring would be perfectly alright in certain cases. But at least it wasn't in our case, and that was worth nothing.
Finally, let's now consider the third and last of the three operators used in conditional rendering in React: the conditional operator (?:
).
The conditional (?:
) operator
There's absolutely no guess work involved with the conditional operator (?:
) as there might be with the logical AND (&&
) and the logical OR (||
) operators. (Well, honestly, there isn't really any guess work involved with the latter two as well.)
The conditional operator is just an expressional if...else
.
When we have two different things to render, depending on the outcome of a condition, we should go for the conditional operator, hands-down.
Let's suppose our Greeting
component takes the info
prop in the form of an object whose text
property contains the text to put inside the <p>
element. So, if info
is provided, info.text
should be used inside the <p>
or else the same old default information.
Now, since we have two different things to do in either case of checking info
, we employ the conditional operator.
Here's the definition of Greeting
:
function Greeting({ name, info }) {
return (
<header>
<h1>Hello {name}!</h1>
<p>{info ? info.text : 'This is the default info.'}</p>
</header>
);
}
Let's now test it.
First, without info
:
function App() {
return (
<Greeting name="Dennis" />
);
}
And now with it:
function App() {
return (
<Greeting name="Dennis" info={{ text: 'Using an object this time.' }} />
);
}
And it works flawlessly!
As we shall explore later on in this course, using the conditional operator is really common in React apps when we ought to indicate loading while some data related to the underlying component is fetched over the network.
When the data is in the process of loading, a loading indicator is shown, but when the data does load completely, some result utilizing that data is rendered.
Conditionally removing attributes
Before we conclude this chapter, it's worthwhile discussing an important point related to conditionally controlling the presence of a given attribute on an element in React.
That is, when an attribute on a React element representing a DOM element is set to null
or undefined
, that attribute is effectively removed from the underlying DOM element node.
For example, suppose we have the following code, with the className
attribute:
<div className="text-blue">Hello</div>
If we inspect the underlying DOM element node in the browser for this React element, we see that it indeed has a class
attribute set on it, with the value "text-blue"
:
<div>
element inspected in the browser.But now let's set the className
attribute to null
:
<div className={null}>Hello</div>
Here's the same element node inspected again:
<div>
element inspected in the browser.See, the attribute has been removed from the element, all thanks to setting it to null
in the JSX.
Keep in mind that setting the className
attribute to true
or false
would produce the same effect:
<div className={false}>Hello</div>
<div>
element inspected in the browser.However, don't get deceived by this behavior of className
; it does NOT apply to every attribute. In particular, it doesn't work for Boolean HTML attributes, such as contentEditable
(remember this is JSX).
In the following code, we set contentEditable
to false
but obviously, as per our expectation, this doesn't remove the attribute but rather sets it to "false"
:
<div contentEditable={false}>Hello</div>
<div>
element inspected in the browser.In HTML, the contenteditable
attribute is a Boolean attribute and so it makes perfect sense for React to follow this approach.
But then how to remove contenteditable
from the HTML if we wish to?
Well, in React the behavior of null
(and undefined
) for an attribute's value is the same across all attributes. That is, null
always leads to an attribute being removed.
So we just need to set contentEditable
to null
:
<div contentEditable={null}>Hello</div>
<div>
element inspected in the browser.Henceforth, for consistency, we'll always set an attribute to null
if we wish to remove it (even if setting it to false would have the same effect) in this course. And we recommend you to follow along.
Consistency is key in programming.