Conditional rendering in React Native may crash your app

koprowski_it

Daniel Koprowski

Posted on May 5, 2020

Conditional rendering in React Native may crash your app

Very soon after start of creating first app every developer needs to render component in one way or another depending on props. When one start searching, the first answer is inside React documentation. The answer is “Conditional Rendering”. But after a while many of us starts facing errors in React Native (or wrong rendering in React for web) that seems to jump on occasionally to reappear some time later. This article will explain what is happening and how to prevent further mistakes.

Conditional rendering using inline If with logical && (AND) operator as React docs says:

You may embed any expressions in JSX by wrapping them in curly braces. This includes the JavaScript logical && operator. It can be handy for conditionally including an element — React docs: Conditional Rendering

{someValue && (
  <View style={styles.field}>
    <Text>{someValue}</Text>
  </View>
)}
Enter fullscreen mode Exit fullscreen mode

This handy solution is used by many and it’s nothing new for our community to see in the code. How and why it can crash your React Native app?

When using it widely in your App without proper attention sooner or later you will see this error (or worse scenario your users will see that the app crashed):

Invariant Violation: Text strings must be rendered within a <Text> component.

Then you see such an error in your logs and scratch your head because usually it works, it may crash for one particular data entry or after some small API change. What happened? Hint: someValue type matters.

The array example

Another common example of javascript operator wrong usage is rendering something is array contains any elements:

// Sooner or later this code will surprise users.
// Just wait for an empty array.
{dataEntries.length && (
  <View>
    <Text>Visible only when array is not empty</Text>
  </View>
)}
Enter fullscreen mode Exit fullscreen mode

Above example looks fine at a first glance. Array’s length will be 0 which is falsy thus condition is not satisfied and following component is not rendered — simple. This reasoning is partially good but author may forgot about one little fact that will surprise users at some point. Let’s take a closer look.

How logical AND && operator in JavaScript works?

Let’s see the docs again:

Operator Syntax Description
Logical AND (&&) expr1 && expr2 If expr1 can be converted to true, returns expr2; else, returns expr1.

[…]If a value can be converted to true, the value is so-called truthy. If a value can be converted to false, the value is so-called falsy.

Examples of expressions that can be converted to false are:

  • null;
  • NaN;
  • 0;
  • empty string ('' [..]);
  • undefined.

MDN docs: Logical operators

Developers love that possibility to treat variables as falsy. Assumption is that when your variable for some reason comes not initialised from backend or other data source you have secured the code from rendering this part of View.

It seems to be a good strategy. We don’t want to show our user nicely formatted undefined string. It’s better to show nothing than null or NaN as well.

Note that description of AND operator says that it returns expr1 or expr2. It always returns one of inputs — not converted. Again: it converts expr1 to Boolean and evaluates result but then returns original value not the converted one. Explained as pseudo code it should look something like this:

if (expr1 == true) {
  return expr2
} else {
  return expr1
}
Enter fullscreen mode Exit fullscreen mode

Basically it is the whole gotcha but let’s dive into examples. I will use Boolean() JavaScript function to show you how values are converted.

String variable.

Boolean('hello world')
// -> true

Boolean('')
// -> false

Boolean(' ')    // space
// -> true

'' && 'conditionally returned string'
// -> ''

'hello world' && 'conditionally returned string'
// -> 'conditionally returned string'
Enter fullscreen mode Exit fullscreen mode

Empty string is falsy so AND operator will return '' because the condition is not fulfilled. Returning '' directly into ReactNative JSX will produce error Text strings must be rendered within a <Text> component and cause crash.

Numeric variable.

Boolean(-1)
// -> true

Boolean(0)
// -> false

Boolean(1)
// -> true

0 && 'conditionally returned string'
// -> 0

1 && 'conditionally returned string'
// -> 'conditionally returned string'
Enter fullscreen mode Exit fullscreen mode

Zero is falsy so logical AND operator will return 0 as the condition is not met. Returning 0 into ReactNative JSX will cause crash with Invariant Violation error again.

Other variable types worth mentioning.

Boolean(null)
// -> false

Boolean(undefined)
// -> false

Boolean({})
// -> true

Boolean([]) // worth noting!
// -> true
Enter fullscreen mode Exit fullscreen mode

From the above examples the most interesting from React Native developer’s point of view is array. Usually when we put array into conditional render we would like not to render anything if array is empty. Passing an empty array into logical expression without any preparation will mislead us. What one would need to do is to check whether length exists and is equal to 0.

Why React Native crashes?

Rendering string in React Native must be wrapped with <Text>...</Text> component. But when we want to hide entire component when variable is empty with conditional rendering it may return an empty string directly into JSX. For example:

let optionalStr = ''

// [...] some logic that leaves `optionalStr` empty

{optionalStr && <Text>{optionalStr}</Text>} // crash
Enter fullscreen mode Exit fullscreen mode

Now you know that above condition is not fulfilled therefore logical AND operator will return optionalStr directly into main JSX.

What about a numeric variable?

Normally, JavaScript expressions inserted in JSX will evaluate to a string, a React element, or a list of those things. […] — React docs: JSX in Depth

React tries to convert results of your expressions into a string, React element or array. This is why you see Invariant Violation: Text strings must be rendered within a <Text> component even if your variable was Number. It may be misleading while searching for this bug in a production code.

Why is it hard to find React Native conditional render error?

This error is sneaky because it may take a long time before it shows up. You code may be working like a charm without any issues for months and suddenly something changes on API and type of that nullable variable changes suddenly to empty string or 0.

Why it works with variables that are null or undefined then? It will also work for booleans. React creators make our life easier and by default such variables are ignored in a JSX tree. It is special case and it will not be rendered.

React will also not crash when you put empty array directly into JSX as arrays can render multiple elements.

// below expressions will not crash your React Native app
<View>
  {false}
  {true}
  {null}
  {undefined}
  {[]}
</View>
Enter fullscreen mode Exit fullscreen mode

React for web — zero appears

Developing a website in React and not converting variable into boolean will also break things but not as much as on native platform. For web empty string or 0 will be rendered. It is normal string and those can be rendered in HTML. For empty string it is usually missed and everything works well as nothing appears on the screen. It may be spotted when one try to conditionally render numeric variable as some strange 0 appears on the site. But nothing crashes and users are not upset as much.

How to make conditional rendering safer?

Just make sure to convert every variable into Boolean before using logical AND && operator.

You can do it multiple ways:

Double negation — !!dangerousData

It’s an easy quick fix that will work and some experiments says that it’s execution time is faster than Boolean(dangerousData).

I do not recommend it though.

This solution’s main pitfall is a human factor. Someone in your team could think that it is pointless to do double negation as it goes from true -> false -> true. It may lead to “refactor” that will create potential crashes in the future as this error may not reveal itself at first. My number one principle while coding is readability.

Classic conversion — Boolean(dangerousData)

This seems readable but as I mentioned above some say that it is slower in execution time so make your own research and decide if it is OK for your particular case. We can find news that in modern browsers it is optimized. You may also use some transpilers to change it before it goes to final code.

Rethink components architecture.

Maybe you don’t need as many conditional renders in the component. Every component should be small and have simplified logic as much as it can. I have seen many overly complicated components with nested conditional renders and believe me it’s not something easy to maintain as your code grows.

Use Element variable

In simple components sometimes you can use trick from React documentation with if and variable assignment preceding return.

// ...
  let message = <Text>'Hello there!'</Text>
  if (isVillain) {
    message = <Text style={styles.deepVoice}>'General React'oni!'</Text>
  }

  return <View>{message}</View>
Enter fullscreen mode Exit fullscreen mode

Component is a function (if else in render)

In class components it would be — render method is a function.

In function, you can call return inside if statement and it will not execute further on. It will have the same result as with Element variable above. We don’t need else here because when condition is satisfied execution will go on, otherwise it will be stopped on first render.

// ...
  if (isVillain) {
    return (
      <View>
        <Text style={styles.deepVoice}>'General React'oni!'</Text>
      </View>
    )
  }

  return (
    <View>
      <Text>'Hello there!'</Text>
    </View>
  )
Enter fullscreen mode Exit fullscreen mode

Conditional (ternary) operator

You can also use conditional operator (ternary expression) condition ? passed : failed but be aware that nesting those will destroy readability of your code. My advice is to set upno-nested-ternary rule for ESLint otherwise your code can become this: const thing = foo ? bar : baz === qux ? quxx : foobar; but with lots more of code because components rises very quick in amount of letters. Multiple elements inside nested ternary operator will make render complicated and unreadable.

// ...
  return (
    <View>
      {isVillain ? (
        <Text style={styles.deepVoice}>'General React'oni!'</Text>
      ) : (
        <Text>'Hello there!'</Text>
      )}
    </View>
  )
Enter fullscreen mode Exit fullscreen mode

Explaining the array example (from the introduction)

Just to remind you I was showing this example:

{dataEntries.length && (
  <View>
    <Text>Visible only when array is not empty</Text>
  </View>
)}
Enter fullscreen mode Exit fullscreen mode

Now you understand that what really happens in above code is returning length to directly into JSX. It happens when length is falsy and it comes from logical operator implementation.

To simplify the example and make things more visible lets assume that dataEntries.length is 0 and following View with Text component is <Component />. Now we have:

{0 && <Component />}
Enter fullscreen mode Exit fullscreen mode

This expression returns 0 which is converted to string '0' and you can see it as an error in React Native or as an extra character on the web.

The quickest fix possible is to make sure that we don’t depend on falsy value but on boolean false.

Here are multiple fix scenarios:

Double negation

{!!dataEntries.length && <Component />}
Enter fullscreen mode Exit fullscreen mode

Classic conversion

{Boolean(dataEntries.length) && <Component />}
Enter fullscreen mode Exit fullscreen mode

Inline condition

{(dataEntries.length > 0) && <Component />}
Enter fullscreen mode Exit fullscreen mode

Ternary operator

{dataEntries.length ? <Component /> : null}
Enter fullscreen mode Exit fullscreen mode

Refactor, rethink, make it safe

let conditionalComponent = null

if(dataEntries.length > 0){
    conditionalComponent = <Component />
}
Enter fullscreen mode Exit fullscreen mode

Do you have other way to render on specific condition? Write it on Twitter or comments under this article. Let’s talk about your observations with this problem.

💖 💪 🙅 🚩
koprowski_it
Daniel Koprowski

Posted on May 5, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related