Conditional rendering against an empty string causes application to crash with cryptic error message
See original GitHub issueDescription
* Note: I’m not sure if this is an issue with React Native, or React itself. If it turns out to be a React problem, I can file an issue over there instead; just let me know.
When using conditional rendering in JSX with the &&
operator, using a variable containing the empty string (""
) as the render condition causes React Native to blow up with the following error message:
Invariant Violation: Text strings must be rendered within a <Text> component.
I did not find the error message to be helpful, and it took several hours of debugging and searching to find the cause of the problem. This StackOverflow question is what finally gave me the information I needed to solve the problem. I found this issue incredibly annoying and time consuming, and I’m sure it would be incredibly helpful to future developers if either (a) React Native just dealt with this issue silently, or (b) the error message provided something more helpful.
React Native version:
System:
OS: macOS 11.0
CPU: (12) x64 Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
Memory: 55.63 MB / 16.00 GB
Shell: 5.8 - /bin/zsh
Binaries:
Node: 12.18.1 - /var/folders/xc/tfq34tzd54qgz7njmw2rvs3w0000gn/T/yarn--1600464068569-0.5536557097021451/node
Yarn: 1.22.4 - /var/folders/xc/tfq34tzd54qgz7njmw2rvs3w0000gn/T/yarn--1600464068569-0.5536557097021451/yarn
npm: 6.14.5 - ~/.nvm/versions/node/v12.18.1/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
Managers:
CocoaPods: 1.9.3 - /Users/kjensen/.gem/bin/pod
SDKs:
iOS SDK:
Platforms: iOS 14.0, DriverKit 19.0, macOS 10.15, tvOS 14.0, watchOS 7.0
Android SDK:
API Levels: 30
Build Tools: 30.0.2
Android NDK: Not Found
IDEs:
Android Studio: 4.1 RC 3 4.1 RC 3
Xcode: 12.0/12A7209 - /usr/bin/xcodebuild
Languages:
Java: 14.0.1 - /usr/bin/javac
Python: 2.7.16 - /usr/bin/python
npmPackages:
@react-native-community/cli: Not Found
react: ~16.13.1 => 16.13.1
react-native: ~0.63.2 => 0.63.2
react-native-macos: Not Found
npmGlobalPackages:
*react-native*: Not Found
Steps To Reproduce
Provide a detailed list of steps that reproduce the issue.
-
Create a JSX or TSX file in your React Native project that includes something like this:
import React from 'react'; import { View, Text } from 'react-native'; export const MyComponent: React.FC = () => { const text: string | undefined = ""; // Presumably, this value would come from some external source, like an API call. return ( <View> {text && <Text>{text}</Text>} </View> ); };
-
Import
MyComponent
and add it to the render function of one of your existing components (likeApp.tsx
). -
Build and run your application.
Expected Results
I expected my <Text/>
component to render if the value of text
was truthy, and not to render if its value was falsy. In the above case, ""
is falsy, so I expect that the component does not render.
Actual Results
The application blows up at runtime with the following exception:
Invariant Violation: Text strings must be rendered within a <Text> component.
Workarounds
Performing explicit boolean coercion on text
allows the component to render properly:
{!!text && <Text>{text}</Text>}
Unfortunately, there is no way that a new developer would know to do this without first encountering the problem, then spending the time to find the solution on StackOverflow or in this issue. It’s also possible that the developer might not ever encounter the issue in development, and might only realize that an issue exists after seeing something about it in production crash logs.
I considered writing a custom ESLint rule to warn about this, but I wasn’t able to find a way to get it working without causing a bunch of false positives, (i.e. using conditional rendering where the condition is actually a boolean), so I think a better solution would be if RN handled this in the platform.
Snack, code example, screenshot, or link to a repository:
See above.
Issue Analytics
- State:
- Created 3 years ago
- Comments:5 (1 by maintainers)
Thanks for explaining your experience in detail!
It’s true that the
&&
operator is tricky in JavaScript and prone to “gotcha” moments like this one. Here’s a doc about it from MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_ANDTo quote from it, take this example:
This is consistent with what’s happening to you;
expr1
in your case is the empty string, which isn’t truthy, so the expression evaluates to the empty string (not, e.g.,false
ornull
, etc.); this would be the case even outside React and React Native.The problem comes when React Native’s
View
component doesn’t want to handle a string (including an empty string) as its direct child; instead, to show some text, React Native wants you to useText
. I think this is accurately indicated by the error you’re seeing:"Invariant Violation: Text strings must be rendered within a <Text> component"
, even if that message by itself isn’t very precise about what lines of your code are causing a problem. In earlier versions of React Native, the error wasn’t a complete sentence like this; it was full of irrelevant-looking identifiers that the app developer isn’t normally concerned about, the crash only occurred on Android (meaning a nasty surprise if you did most of your testing on iOS), and (IIRC) left no trail in the component hierarchy that would lead you to the specific component that had to be examined. Do you at least get a trail like that?The React docs make a brief mention of this strategy for conditional rendering. But (perhaps disingenuously) it only covers the left-hand side being
true
orfalse
, and not what happens if it’s merely truthy or falsy—leaving it up to the programmer’s luck of how familiar they are with the quirks of&&
.I think in general being making sure the left-hand side is a boolean is a good pattern. In React on the web, rendering the empty string as a child of an element, for example, is allowed, just as it obviously is in plain HTML. But if the case were slightly different, and the left-hand side were a number (e.g., with
{things.length && <ListOfThings things={things} />
), you could end up with an unexpected0
appearing somewhere on the page and have no idea how it got there.Anyway, a few points for where I think this could go:
this will lead to
Text strings must be rendered within a <Text> component.
error in my casein fact this code result
and
react native
do not allow text inside aView
component