Do you want to request a _feature_ or report a _bug_?
Report a bug
What is the current behavior?
Running ReactTestUtils.createRenderer().render() causes react to throw an exception:
Warning: Exception thrown by hook while handling onSetChildren: Invariant Violation: Expected onBeforeMountComponent() parent and onSetChildren() to be consistent (3 has parents 0 and 2).
Invariant Violation: Expected onBeforeMountComponent() parent and onSetChildren() to be consistent (3 has parents 0 and 2).
I have the same issue when using both Component and PureComponent.
I do _not_ see the error when using ReactTestUtils.renderToDocument().
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar (template: https://jsfiddle.net/reactjs/69z2wepo/).
Could not figure out how to use ReactTestUtils in that JSFiddle example even though react-with-addons is loaded.
Here's the code I used in my project to reproduce it:
import React, { Component, PureComponent } from 'react'
import { render } from 'react-dom'
import ReactTestUtils from 'react-addons-test-utils'
class Root extends Component {
componentWillMount() {
this.testBreak()
setTimeout(this.testBreak.bind(this), 10)
}
testBreak() {
var i = 0, l = 50
while(i <= l) {
this.setState({ test: `this will break testing ${++i}` })
}
}
render() {
return (
<div>
{/* You need more than one nested element for this to break */}
<div>First div</div>
<div>Second div</div>
</div>
)
}
}
render(
<Root />
, document.getElementById('root'))
class TestReactElement extends PureComponent {
render() { return (
<div>{this.props.name}</div>
)}
}
const testRender = (name) => {
let renderer = ReactTestUtils.createRenderer()
renderer.render(<TestReactElement name={name} />)
}
testRender('Test 1')
testRender('Test 2')
testRender('Test 3')
It works fine so long as I run testRender() once. As soon as it's run 2 or more times, the error pops up.
In my project, TAP output is being listened to from a console.log wrapper and state is being updated when a new one comes in. So while my reproducible example is contrived, it's real-world bug when running tests in my project.
What is the expected behavior?
The expected behavior is that no error occurs in the console when running multiple shallow test renders while at the same time React is updating the state of an element rendered to the DOM.
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
15.3.0 and 15.3.2. Windows 10 64-bit v1607 (and updates up to October 10th, 2016). I've not tried this in previous versions of React. Verified it displays the error in Chrome 54.0.2840.50 beta-m (64-bit) and Firefox 50.0b5.
Here's an updated fail case with less code and which works in node:
'use strict'
const React = require('react')
const enzyme = require('enzyme')
const jsdom = require('jsdom')
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>')
global.document = doc
global.window = doc.defaultView
class Root extends React.Component {
componentWillMount() {
// Will work if this is not deferred
setTimeout(this.testBreak.bind(this), 1)
}
testBreak() {
// Will work if we do not setState here
this.setState({})
}
render() {
// Will work if we remove at least one `a`. This bug manifests itself with multiple children.
// It doesn't matter if the children are in an Array.
return React.createElement('div', null, React.createElement('a'), React.createElement('a'))
}
}
enzyme.mount(React.createElement(Root))
const TestReactElement = () => React.createElement('div')
// Will work if we use `mount` instead of shallow rendering
const testRender = () => enzyme.shallow(
React.createElement(TestReactElement)
)
// Will work if we call testRender < 3x
testRender()
testRender()
testRender()
Edit: updated with code comments.
Here's another example that uses a pubsub event-emitter which simulates what a test would look like if it was hooked up to something like flux:
'use strict'
const React = require('react')
const enzyme = require('enzyme')
const jsdom = require('jsdom')
const transmitter = require('transmitter')
const bus = transmitter()
const doc = jsdom.jsdom('<!doctype html><html><body></body></html>')
global.document = doc
global.window = doc.defaultView
class Root extends React.Component {
componentDidMount() {
// Flux-like
bus.subscribe(() => this.setState({}))
}
render() {
// Will work if we remove at least one `a`. This bug manifests itself with multiple children.
// It doesn't matter if the children are in an Array.
return React.createElement('div', null, React.createElement('a'), React.createElement('i'))
}
}
// Works if we use shallow rendering here
enzyme.mount(React.createElement(Root))
const TestReactElement = () => React.createElement('section')
// Will work if we use `mount` instead of shallow rendering
const testRender = () => enzyme.shallow(
React.createElement(TestReactElement)
)
// Will work if we call testRender < 3x
testRender()
testRender()
testRender()
// Once we call an action in a separate test, it'll fail.
bus.publish(2)
Most helpful comment
Here's an updated fail case with less code and which works in node:
Edit: updated with code comments.