Given a simple component like this:
import * as React from "react";
interface Data {
value: string;
}
export class SimpleComponent extends React.Component<Data, Data> {
protected static getDerivedStateFromProps(props: Data, state: Data) {
if (props.value === state.value) {
return null;
}
console.log("updating state: ", props.value);
return {
value: props.value
};
}
public state: Data = {
value: this.props.value
};
public shouldComponentUpdate(nextProps: Data, nextState: Data) {
console.log("state: ", this.state);
console.log("nextState: ", nextState);
console.log("props: ", this.props);
console.log("nextProps: ", nextProps);
const shouldUpdate = nextState.value !== this.state.value;
console.log("shouldUpdate: ", shouldUpdate);
return shouldUpdate;
}
public render() {
console.log("render: ", this.state.value);
return (
<input value={this.props.value} />
);
}
}
I would expect the following test to pass:
import * as React from "react";
import { Expect, Test, TestFixture, FocusTest } from "alsatian";
import { shallow } from "enzyme";
import { SimpleComponent } from "./SimpleComponent";
@TestFixture("SimpleComponent")
export class SimpleComponentTests {
@FocusTest
@Test("should update value from props")
public shouldUpdateValueFromProps() {
const wrapper = shallow(<SimpleComponent value="initial" />);
Expect(wrapper.find("input").prop("value")).toEqual("initial");
wrapper.setProps({ value: "updated" });
Expect(wrapper.find("input").prop("value")).toEqual("updated");
}
}
However it fails as shouldComponentUpdate returns false. Looking at the logs we see the following:
# FIXTURE SimpleComponent
render: initial
updating state: updated
state: { value: 'updated' }
nextState: { value: 'updated' }
props: { value: 'initial' }
nextProps: { value: 'updated' }
shouldUpdate: false
not ok 1 should update value from props
---
message: "Expected \"initial\" to be equal to \"updated\"."
severity: fail
data:
got: "initial"
expect: "updated"
...
As you can see from the above this.state appears to have already been updated by the time shouldComponentUpdate gets called resulting in it returning false and the test failing as it never re-renders.
If I run the same component in the browser I get the following output:
render: initial
updating state: updated
state: {value: "initial"}
nextState: {value: "updated"}
props: {value: "initial"}
nextProps: {value: "updated"}
shouldUpdate: true
render: updated
Which is what I would expect.
The state of the component should not be updated before calling shouldComponentUpdate.
not sure whats needed here
| library | version
| ------------------- | -------
| enzyme | ^3.8.0
| react | ^16.3.0
| react-dom | ^16.5.0
| react-test-renderer | ~16.5.2
| adapter (below) | ^1.7.1
Try wrapper.update() after setProps?
@ljharb just tried it and it doesn't help
@hisuwh I see that shouldUpdate in the shouldComponentUpdate is computed by comparing props difference in the code snippet you provide , this should diff state right?
If so, I can reproduce that it works as expected behavior in browser(reproduce here). I also checked it will work in mount, and not work in shallow in enzyme test environment:
class SimpleComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
value: this.props.value
}
}
static getDerivedStateFromProps(props, state) {
if (props.value === state.value) {
return null;
}
console.log("updating state: ", props.value);
return {
value: props.value
};
}
shouldComponentUpdate(nextProps, nextState) {
console.log("state: ", this.state);
console.log("nextState: ", nextState);
console.log("props: ", this.props);
console.log("nextProps: ", nextProps);
const shouldUpdate = nextState.value !== this.state.value;
console.log("shouldUpdate: ", shouldUpdate);
return shouldUpdate;
}
render() {
console.log("render: ", this.state.value);
return (
<input value={this.props.value} />
);
}
}
// the test will pass with `mount`
// won't pass with `shallow`
const wrapper = mount(<SimpleComponent value="initial" />);
expect(wrapper.find("input").prop("value")).to.equal("initial");
wrapper.setProps({ value: "updated" });
expect(wrapper.find("input").prop("value")).to.equal("updated");
})
@chenesan yh it should have been state - I've corrected it.
I was testing to see if the same happened with props and didn't revert it before posting this sorry.
I have changed my test code to use mount as a workaround - though this is not ideal as I now need to handle more of the inner logic of the component.
Shallow is implementing most of these lifecycle methods correctly but not this one.
ok, I'll look into this.
After some investigation I found out that react-test-renderer/shallow doesn't handle set state in gDSFP properly. Have created an issue in facebook/react#14607 and waited for reply. If they regard this as a bug then I'll work on that.
@hisuwh the fix is merged into react and I think this issue should be fixed after react publishing new patch version.
@chenesan once the react-test-render change is released, would you open a PR with the relevant test cases, and a version bump? <3 please feel free to open the PR with tests ASAP :-D
I'll send PR after fix in react-test-renderer is released ;)
Thanks for your hard work @ljharb @chenesan
Most helpful comment
I'll send PR after fix in react-test-renderer is released ;)