Description
Apologies in advance if this has already been reported. I did my best to thoroughly search through existing issues before posting this, but could not find something this specific. I'd be happy to move/close this as needed. This is (hopefully) more nuanced than the issue about "my render method threw and React blew up, why?" that I mostly encountered.
Do you want to request a feature or report a bug?
Bug.
Minimal Test Cases
Uncaught TypeError: Cannot read property '_currentElement' of null
Uncaught TypeError: Cannot read property 'getNativeNode' of null
What is the current behavior?
If a child component throws in its render
method, and attempts to be rendered more than once, React throws an uncaught error inside of its internal _updateRenderedComponent
method. Depending on the context, this error is either:
Uncaught TypeError: Cannot read property 'getNativeNode' of null
Uncaught TypeError: Cannot read property '_currentElement' of null
Let's say we have a problematic component A
. When A
renders it throws a runtime error.
class A extends React.Component {
render () {
throw new Error('whoops')
}
}
- Some parent component renders
A
. During the first render, we see the error thatA
threw while rendering. This is as expected and is a userland error. - If we attempt to render the parent again, and therefore again render
A
, we get a different exception. This is where we seeUncaught TypeError: Cannot read property 'getNativeNode' of null
from React (or the one referring to_currentElement
).
The difference between exceptions #1 (getNativeNode
) and #2 (_currentElement
) is that #1 happens when component A
is the same between each render; #2 happens when component A
is swapped in the second render with another component that also happens to throw while rendering. The latter is more likely as we move from a loaded -> error handler view, where the error view may unfortunately also throw.
Why would you render a broken twice? Well, consider something as simple as this:
class AppLoaded extends React.Component {
render () {
throw new Error('I throw!')
}
}
// Supposed to gracefully render an error, but it happens to throw.
class GracefulError extends React.Component {
render () {
throw new Error('Dang, I also throw :(')
}
}
class Main extends React.Component {
constructor(props, ctx) {
super(props, ctx)
this.state = {
loaded: false,
error: null,
}
}
componentDidMount () {
// Do some async work...
// NOTE: This living inside of a promise is not vital to reproduction, but
// it is what originally caused me to go down the path of discovering
// the issue. See minimal test cases for examples w/o promises.
return Promise.resolve() // do some async work
.then(() => {
this.setState({ loaded: true })
})
// Yes, this really should not be in `catch`, but there are valid scenarios
// for code like this to exist. This is just to demonstrate the issue.
// See explanations below.
.catch((err) => {
this.setState({ error: true })
})
}
render () {
if (this.state.error) return <GracefulError />
if (this.state.loaded) return <AppLoaded />
return <h1>Loading</h1>
}
}
This is what led to the discovery of this bug. The promise handling here is technically incorrect, since what we really meant was to use .then(onSuccess, onFailure)
so that we didn't implicitly catch synchronous render errors caused by the setState
call in the onSuccess
callback. However, I do believe there are valid scenarios where this occurs, such as when manually handling a render error, but where the recovering component rendered in its place also (unfortunately) throws and consequently leads to this untraceable error.
What is the expected behavior?
React should not blow up with this internal/untraceable error on the second render. I'm not even sure if this is possible, but at a minimum I'd like to better understand why this occurs.
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
Browser: Google Chrome Version 58.0.3029.81 (64-bit)
React:
- v15.1.0
- v15.3.2
- v15.5.0
Those are just the versions I've tested, there's no evidence that it works in any versions in between those posted above. No, this has not worked in a previous version to my knowledge.
Thank you in advance for your help.