Description
Do you want to request a feature or report a bug?
Bug
What is the current behavior?
It seems that, if the children of a new-style React.createContext()
context Provider
are constant, the Provider
can block updates from old-style this.context
context providers from propagating to this.context
consumers.
This sandbox demonstrates the issue. Clicking the button with a number will correctly increment the Root
's state
and context
, but the update is only propagated to the Child3
's context
(and its button) when the "Colors!" button is clicked, as it causes an update to the value
of the new-style Provider
:
https://codesandbox.io/s/ol4lpokpjy
Copy of the source code in the sandbox
import PropTypes from "prop-types";
import React from "react";
import ReactDOM from "react-dom";
class Root extends React.Component {
constructor(props: {}) {
super(props);
this.state = {
count: 0
};
this.countUp = this.countUp.bind(this);
}
getChildContext() {
return {
...this.context,
count: this.state.count,
countUp: this.countUp
};
}
render() {
return this.props.children;
}
countUp() {
this.setState(({ count }) => ({ count: count + 1 }));
}
}
Root.childContextTypes = {
count: PropTypes.number.isRequired,
countUp: PropTypes.func.isRequired
};
const ctx = React.createContext();
class Child1 extends React.Component {
constructor(props: { onClick(): void }) {
super(props);
this.state = {
color: randomHexColor(),
newColor: this.newColor.bind(this)
};
}
render() {
return (
<ctx.Provider value={this.state}>{this.props.children}</ctx.Provider>
);
}
newColor() {
const color = randomHexColor();
this.setState(() => ({ color }));
}
}
function randomHexColor() {
const colorStr = Math.floor(Math.random() * (Math.pow(2, 24) - 1)).toString(
16
);
return "#000000".slice(0, -colorStr.length) + colorStr;
}
class Child2 extends React.Component {
render() {
return (
<ctx.Consumer>
{ctx => (
<React.Fragment>
<Child3 color={ctx.color} />
<button onClick={ctx.newColor}>Colors!</button>
</React.Fragment>
)}
</ctx.Consumer>
);
}
}
class Child3 extends React.Component {
render() {
return (
<button
style={{ color: this.props.color }}
onClick={this.context.countUp}
>
{this.context.count}
</button>
);
}
}
Child3.contextTypes = {
count: PropTypes.number.isRequired,
countUp: PropTypes.func.isRequired
};
ReactDOM.render(
<Root>
<Child1>
<Child2 />
</Child1>
</Root>,
document.getElementById("root")
);
What is the expected behavior?
Both old-style and new-style context updates should coexist.
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
16.3.1; also broken in 16.3.0.
This seems to only happen if the children
of the Provider
are constant, which is what happens when the children are provided on the first and only ReactDOM.render
call. If Child1
is updated to directly use <Child2/>
instead of {this.props.children}
, the problem does not happen.
This can also be a problem when using a production optimization that hoists constant elements outside the Component if the specified children are constant, which would even defeat the fix/workaround for the example above.