Skip to content

Commit

Permalink
Merge 442fdcb into 7e49a91
Browse files Browse the repository at this point in the history
  • Loading branch information
marvinhagemeister committed May 22, 2018
2 parents 7e49a91 + 442fdcb commit b1bce35
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 2 deletions.
9 changes: 7 additions & 2 deletions src/vdom/component.js
Expand Up @@ -68,13 +68,14 @@ export function renderComponent(component, opts, mountAll, isChild) {
state = component.state,
context = component.context,
previousProps = component.prevProps || props,
previousState = component.prevState || state,
previousState = extend({}, component.prevState || state),
previousContext = component.prevContext || context,
isUpdate = component.base,
nextBase = component.nextBase,
initialBase = isUpdate || nextBase,
initialChildComponent = component._component,
skip = false,
snapshot = previousContext,
rendered, inst, cbase;

if (component.constructor.getDerivedStateFromProps) {
Expand Down Expand Up @@ -110,6 +111,10 @@ export function renderComponent(component, opts, mountAll, isChild) {
context = extend(extend({}, context), component.getChildContext());
}

if (isUpdate && component.getSnapshotBeforeUpdate) {
snapshot = component.getSnapshotBeforeUpdate(previousProps, previousState);
}

let childComponent = rendered && rendered.nodeName,
toUnmount, base;

Expand Down Expand Up @@ -187,7 +192,7 @@ export function renderComponent(component, opts, mountAll, isChild) {
// flushMounts();

if (component.componentDidUpdate) {
component.componentDidUpdate(previousProps, previousState, previousContext);
component.componentDidUpdate(previousProps, previousState, snapshot);
}
if (options.afterUpdate) options.afterUpdate(component);
}
Expand Down
175 changes: 175 additions & 0 deletions test/browser/lifecycle.js
Expand Up @@ -218,6 +218,181 @@ describe('Lifecycle methods', () => {
// [should not override state with stale values if prevState is spread within getDerivedStateFromProps](https://github.com/facebook/react/blob/25dda90c1ecb0c662ab06e2c80c1ee31e0ae9d36/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js#L1035)
});

describe("#getSnapshotBeforeUpdate", () => {
it('should call nested new lifecycle methods in the right order', () => {
let log;
const logger = function(msg) {
return function() {
// return true for shouldComponentUpdate
log.push(msg);
return true;
};
};
class Outer extends Component {
static getDerivedStateFromProps() {
log.push('outer getDerivedStateFromProps');
return null;
}
render() {
return (
<div>
<Inner x={this.props.x} />
</div>
);
}
}

Object.assign(Outer.prototype, {
componentDidMount: logger('outer componentDidMount'),
shouldComponentUpdate: logger('outer shouldComponentUpdate'),
getSnapshotBeforeUpdate: logger('outer getSnapshotBeforeUpdate'),
componentDidUpdate: logger('outer componentDidUpdate'),
componentWillUnmount: logger('outer componentWillUnmount')
});

class Inner extends Component {
static getDerivedStateFromProps() {
log.push('inner getDerivedStateFromProps');
return null;
}
render() {
return <span>{this.props.x}</span>;
}
}
Object.assign(Inner.prototype, {
componentDidMount: logger('inner componentDidMount'),
shouldComponentUpdate: logger('inner shouldComponentUpdate'),
getSnapshotBeforeUpdate: logger('inner getSnapshotBeforeUpdate'),
componentDidUpdate: logger('inner componentDidUpdate'),
componentWillUnmount: logger('inner componentWillUnmount')
});

log = [];
render(<Outer x={1} />, scratch);
expect(log).to.deep.equal([
'outer getDerivedStateFromProps',
'inner getDerivedStateFromProps',
'inner componentDidMount',
'outer componentDidMount'
]);

// Dedup warnings
log = [];
render(<Outer x={2} />, scratch, scratch.firstChild);
// Note: we differ from react here in that we apply changes to the dom
// as we find them while diffing. React on the other hand separates this
// into specific phases, meaning changes to the dom are only flushed
// once the whole diff-phase is complete. This is why
// "outer getSnapshotBeforeUpdate" is called just before the "inner" hooks.
// For react this call would be right before "outer componentDidUpdate"
expect(log).to.deep.equal([
'outer getDerivedStateFromProps',
'outer shouldComponentUpdate',
'outer getSnapshotBeforeUpdate',
'inner getDerivedStateFromProps',
'inner shouldComponentUpdate',
'inner getSnapshotBeforeUpdate',
'inner componentDidUpdate',
'outer componentDidUpdate'
]);
});

it('should pass the return value from getSnapshotBeforeUpdate to componentDidUpdate', () => {
let log = [];

class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
value: 0
};
}
static getDerivedStateFromProps(nextProps, prevState) {
return {
value: prevState.value + 1
};
}
getSnapshotBeforeUpdate(prevProps, prevState) {
log.push(
`getSnapshotBeforeUpdate() prevProps:${prevProps.value} prevState:${
prevState.value
}`,
);
return 'abc';
}
componentDidUpdate(prevProps, prevState, snapshot) {
log.push(
`componentDidUpdate() prevProps:${prevProps.value} prevState:${
prevState.value
} snapshot:${snapshot}`,
);
}
render() {
log.push('render');
return null;
}
}

render(<MyComponent value="foo" />, scratch);
expect(log).to.deep.equal(['render']);
log = [];

render(<MyComponent value="bar" />, scratch, scratch.firstChild);
expect(log).to.deep.equal([
'render',
'getSnapshotBeforeUpdate() prevProps:foo prevState:1',
'componentDidUpdate() prevProps:foo prevState:1 snapshot:abc'
]);
log = [];

render(<MyComponent value="baz" />, scratch, scratch.firstChild);
expect(log).to.deep.equal([
'render',
'getSnapshotBeforeUpdate() prevProps:bar prevState:2',
'componentDidUpdate() prevProps:bar prevState:2 snapshot:abc'
]);
log = [];

render(<div />, scratch, scratch.firstChild);
expect(log).to.deep.equal([]);
});

it('should call getSnapshotBeforeUpdate before mutations are committed', () => {
let log = [];

class MyComponent extends Component {
getSnapshotBeforeUpdate(prevProps) {
log.push('getSnapshotBeforeUpdate');
expect(this.divRef.textContent).to.equal(
`value:${prevProps.value}`,
);
return 'foobar';
}
componentDidUpdate(prevProps, prevState, snapshot) {
log.push('componentDidUpdate');
expect(this.divRef.textContent).to.equal(
`value:${this.props.value}`,
);
expect(snapshot).to.equal('foobar');
}
render() {
log.push('render');
return <div ref={ref => this.divRef = ref}>{`value:${this.props.value}`}</div>;
}
}

render(<MyComponent value="foo" />, scratch);
expect(log).to.deep.equal(['render']);
log = [];

render(<MyComponent value="bar" />, scratch, scratch.firstChild);
expect(log).to.deep.equal([
'render',
'getSnapshotBeforeUpdate',
'componentDidUpdate'
]);
});
});

describe('#componentWillUpdate', () => {
it('should NOT be called on initial render', () => {
Expand Down

0 comments on commit b1bce35

Please sign in to comment.