common mistakes

Incorrect Usage of Logical Operators

The code provided in this article is simplified as much as possible to avoid overwhelming the reader with code and to convey the essence of the problem and its solution. In interactive examples, heading tags (`h1...h6`) are replaced with span tags to prevent potential conflicts with the main page structure.

In most components, it's common to work with multiple logical values for displaying or hiding certain HTML elements on the page. This is perfectly normal. However, there are several different ways to handle this logic within a component. The most commonly used is the && operator. Although it's fully functional in JavaScript, it can lead to some unforeseen consequences in your user interface. Let's consider an example:

Code implementation
1const totalMessages = 0;
2
3const MessageNotifier: FC = () => {
4 return (
5 <div>
6 <h3>MessageNotifier</h3>
7 {totalMessages && (
8 <span>You have {totalMessages} unread messages</span>
9 )}
10 <p>Some content</p>
11 </div>
12 );
13};
14

In this example, the component should display the number of unread messages. However, if totalMessages equals zero, only 0 will be displayed on the page. This happens because in JavaScript, the value 0 is considered falsy. The && operator returns a falsy value if either of its operands is falsy, in this case, when totalMessages equals 0. And since the condition is falsy, React won't render the second argument — the message, and instead will display the value of totalMessages, which is a number. React displays values that are of type string or number. When using this syntax for conditional rendering, it's better to explicitly check the logical value, rather than relying on type coercion in JavaScript. This helps to avoid unexpected side effects.

Code Improvement Options

Using a Boolean Value

Code implementation
1const totalMessages = 0;
2
3const MessageNotifier: FC = () => {
4 const hasMessages = totalMessages > 0;
5
6 return (
7 <div>
8 <h3>MessageNotifier</h3>
9 {hasMessages && (
10 <span>You have {totalMessages} unread messages</span>
11 )}
12 <p>Some content</p>
13 </div>
14 );
15};
16

Here, we first create a boolean variable hasMessages, which stores the result of checking whether totalMessages has a value greater than zero. Then we use this variable for conditional rendering of the text. This makes the code more understandable and manageable, improving its readability.

Using Double Exclamation Marks

Code implementation
1const totalMessages = 0;
2
3const MessageNotifier: FC = () => {
4 return (
5 <div>
6 <h3>MessageNotifier</h3>
7 {!!totalMessages && (
8 <span>You have {totalMessages} unread messages</span>
9 )}
10 <p>Some content</p>
11 </div>
12 );
13};
14

Double exclamation marks convert the number of unread messages into a boolean value. A value of 0 will be converted to false, resulting in nothing being displayed on the page. However, if the count is a truthy value (i.e., not 0, null, undefined, or NaN), the specified expression after && will be evaluated and rendered. This approach improves code readability and simplifies maintenance.

Splitting into Separate Components with Explicit Condition Checking

Code implementation
1const totalMessages = 0;
2
3interface MessageProps {
4 totalMessages: number;
5}
6
7const Message: FC<MessageProps> = ({ totalMessages }) => {
8 if (totalMessages <= 0) {
9 return null;
10 }
11
12 return <span>You have {totalMessages} unread messages</span>;
13};
14
15const MessageNotifier: FC = () => {
16 return (
17 <div>
18 <h3>MessageNotifier</h3>
19 <Message totalMessages={totalMessages} />
20 <p>Some content</p>
21 </div>
22 );
23};
24

Sometimes it makes sense to split a component with conditional rendering into separate components. In this example, the logic of checking is moved to a separate standalone component Message, which is responsible only for displaying the message about the number of unread messages. Inside this component, we explicitly check the value of totalMessages and return null if it's less than or equal to zero. This ensures that the component won't render if there are no unread messages. Additionally, this helps to make the code more modular and easier to maintain, enhancing readability and enabling code reuse. Of course, this solution is more suitable for more complex components, unlike this example.

Using the Ternary Operator

Code implementation
1const totalMessages = 0;
2
3const MessageNotifier: FC = () => {
4 return (
5 <div>
6 <h3>MessageNotifier</h3>
7 {totalMessages ? (
8 <span>You have {totalMessages} unread messages</span>
9 ) : null}
10 <p>Some content</p>
11 </div>
12 );
13};
14

Ternary operators can elegantly solve the problem by returning null if there are no unread messages. This method makes the code more compact and readable since the condition and the rendering result are in the same place. Such a solution enhances the perception of logic and simplifies code maintenance.

As you can see, there are multiple approaches to conditional rendering in React, and the choice of a specific method may depend on personal preferences, project requirements, and the component itself. However, it's important to remember that implicit type coercion in JavaScript creates unexpected side effects and makes the code less reliable and readable.

Last updated on by @skrykateSuggest an improvement on Github repository.