React conditional rendering methods
Preface
In React, you can create distinct components that encapsulate behavior you need. Then, you can render only some of them,
depending on the state of your application. JSX is a powerful extension to JavaScript that allows us to define UI components.
But it doesn’t support loops or conditional expressions directly. If you want to iterate over a list to render more than one
component or implement some conditional logic, you have to use pure Javascript. You don’t have a lot of options with looping
either. Most of the time, map
will cover your needs.
Obviously, there is a way around it. This is where the concept of conditional rendering is brought. In a conditional render a component decides based on one or several conditions which elements it will return. For instance, it can either return a list of items or a message that says “Sorry, the list is empty”. When a component has conditional rendering, the instance of the rendered component can have different looks. There’s more than one way to use conditional expressions in React. And, as with most things in programming, some are better suited than others depending on the problem you’re trying to solve.
Types of conditional rendering methods
If/else
The easiest way to have a conditional rendering is to use an if else in React in your render method. Imagine you don’t want to render your component, because it doesn’t have the necessary props.
Let’s create a component with the following state:
1
2
3
4
5
6
class App extends React.Component {
constructor(props) {
super(props);
this.state = {text: '', inputText: '', mode:'view'};
}
}
We’ll use one property for the saved text and another one or the text that is being edited. A third property will indicate if
you are in edit
or view
mode.
Next, add some methods for handling input text and the save
and edit
events:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class App extends React.Component {
constructor(props) {
super(props);
this.state = {text: '', inputText: '', mode:'view'};
this.handleChange = this.handleChange.bind(this);
this.handleSave = this.handleSave.bind(this);
this.handleEdit = this.handleEdit.bind(this);
}
handleChange(e) {
this.setState({ inputText: e.target.value });
}
handleSave() {
this.setState({text: this.state.inputText, mode: 'view'});
}
handleEdit() {
this.setState({mode: 'edit'});
}
}
Now for the render
method, check the mode state property to either render an edit button or a text input and a save button,
in addition to the saved text:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class App extends React.Component {
// …
render () {
if(this.state.mode === 'view') {
return (
<div>
<p>Text: {this.state.text}</p>
<button onClick={this.handleEdit}>
Edit
</button>
</div>
);
} else {
return (
<div>
<p>Text: {this.state.text}</p>
<input
onChange={this.handleChange}
value={this.state.inputText}
/>
<button onClick={this.handleSave}>
Save
</button>
</div>
);
}
}
An if/else block is the easiest way to solve the problem, but this is not a good implementation.
It works great for simple use cases and every programmer knows how it works. But there’s a lot of repetition and the render method looks crowded.
So let’s simplify it by extracting all the conditional logic to two render methods, one to render the input box and another one to render the button:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class App extends React.Component {
// …
renderInputField() {
if(this.state.mode === 'view') {
return <div></div>;
} else {
return (
<p>
<input
onChange={this.handleChange}
value={this.state.inputText}
/>
</p>
);
}
}
renderButton() {
if(this.state.mode === 'view') {
return (
<button onClick={this.handleEdit}>
Edit
</button>
);
} else {
return (
<button onClick={this.handleSave}>
Save
</button>
);
}
}
render () {
return (
<div>
<p>Text: {this.state.text}</p>
{this.renderInputField()}
{this.renderButton()}
</div>
);
}
}
Notice that the method renderInputField
returns an empty div element when the app is in view mode.
However, this is not necessary. We can prevent this through the usage of null.
Prevent rendering with null
If you want to hide a component, you can make its render method return null
, there’s no need to render an empty
(and different) element as a placeholder.
One important thing to keep in mind when returning null
is that even though the component doesn’t show up, its lifecycle
methods are still fired.
Back to our example, change the renderInputField
method to look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
renderInputField() {
if(this.state.mode === 'view') {
return null;
} else {
return (
<p>
<input
onChange={this.handleChange}
value={this.state.inputText}
/>
</p>
);
}
}
One advantage of returning null
instead of an empty element is that you’ll improve a little bit the performance of your app
because React won’t have to unmount the component to replace it.
Element Variables
One thing that is an inconvenience is having more than one return statement in methods.
To solve this lets use a variable to store the JSX elements and only initialize it when the condition is true:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
renderInputField() {
let input;
if(this.state.mode !== 'view') {
input =
<p>
<input
onChange={this.handleChange}
value={this.state.inputText} />
</p>;
}
return input;
}
renderButton() {
let button;
if(this.state.mode === 'view') {
button =
<button onClick={this.handleEdit}>
Edit
</button>;
} else {
button =
<button onClick={this.handleSave}>
Save
</button>;
}
return button;
}
The main render
method is more readable this way but maybe it isn’t necessary to use if/else blocks (or something like a
switch statement) and secondary render methods.
Ternary operator
Instead of using an if/else block we can use the ternary conditional operator:
Syntax: condition ? expr_if_true : expr_if_false
The operator is wrapped in curly braces optionally wrapped in parentheses to improve readability.
Let’s apply it to the example so you can see this in action. We’ll remove the renderInputField
and renderButton
and in the
render
method, let’s add a variable to know if the component is in view or edit mode:
1
2
3
4
5
6
7
8
render () {
const view = this.state.mode === 'view';
return (
<div>
</div>
);
}
Now you can use the ternary operator to return null
if the view
mode is set, or the input field otherwise:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
return (
<div>
<p>Text: {this.state.text}</p>
{
view
? null
: (
<p>
<input
onChange={this.handleChange}
value={this.state.inputText} />
</p>
)
}
</div>
);
Using a ternary operator, you can declare one component to render either a save or edit button by changing its handler and label correspondingly:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
return (
<div>
<p>Text: {this.state.text}</p>
{
...
}
<button
onClick={
view
? this.handleEdit
: this.handleSave
} >
{view ? 'Edit' : 'Save'}
</button>
</div>
);
Short circuit operator
The ternary operator has a special case where it can be simplified. When you want to render either something or nothing, you can only use the && operator. Unlike the & operator, && doesn’t evaluate the right-hand side expression if just by evaluating the left-hand expression can decide the final result.
For example, if the first expression evaluates to false (false && …), it’s not necessary to evaluate the next expression because the result will always be false.
In React, you can have expressions like the following:
1
2
3
4
5
return (
<div>
{ showHeader && <Header /> }
</div>
);
If showHeader
evaluates to true, the
If showHeader
evaluates to false, the
This way, the following expression:
1
2
3
4
5
6
7
8
9
10
11
{
view
? null
: (
<p>
<input
onChange={this.handleChange}
value={this.state.inputText} />
</p>
)
}
can be turned into:
1
2
3
4
5
6
7
!view && (
<p>
<input
onChange={this.handleChange}
value={this.state.inputText} />
</p>
)
Higher-order components
A higher-order component (HOC) is a function that that takes an existing component and returns a new one with some added functionality. Applied to conditional rendering, an HOC could return a different component than the one passed based on some condition:
1
2
3
4
5
6
7
8
9
function higherOrderComponent(Component) {
return function EnhancedComponent(props) {
if (condition) {
return <AnotherComponent { ...props } />;
}
return <Component { ...props } />;
};
}
In functional programming, the Either type is commonly used as a wrapper to return two different values.
So let’s start by defining a function that takes two arguments, another function that will return a boolean value (the result
of the conditional evaluation), and the component that will be returned if that value is true
.
It’s a convention to start the name of the HOC with the word with
. This function will return another function that will take
the original component to return a new one:
1
2
3
4
5
function withEither(conditionalRenderingFn, EitherComponent) {
return function buildNewComponent(Component) {
}
}
The inner functions have access to the outer functions’ parameters, so now, based on the value returned by the function
conditionalRenderingFn
, you either return the EitherComponent
or the original Component:
1
2
3
4
5
6
7
8
9
function withEither(conditionalRenderingFn, EitherComponent) {
return function buildNewComponent(Component) {
return function FinalComponent(props) {
return conditionalRenderingFn(props)
? <EitherComponent { ...props } />
: <Component { ...props } />;
}
}
}
This way you can create a withEditConditionalRendering
HOC and with this, create a EditSaveWithConditionalRendering
component:
1
2
3
const isViewConditionFn = (props) => props.mode === 'view';
const withEditContionalRendering = withEither(isViewConditionFn, EditComponent);
const EditSaveWithConditionalRendering = withEditContionalRendering(SaveComponent);
Then you can use in the render method passing all the properties needed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
render () {
return (
<div>
<p>Text: {this.state.text}</p>
<EditSaveWithConditionalRendering
mode={this.state.mode}
handleEdit={this.handleEdit}
handleChange={this.handleChange}
handleSave={this.handleSave}
text={this.state.inputText}
/>
</div>
);
}