Skip to content

Commit

Permalink
backport #3763 and #3743
Browse files Browse the repository at this point in the history
  • Loading branch information
JoviDeCroock committed Oct 26, 2022
1 parent 0a7d4bd commit f892739
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/component.js
Expand Up @@ -53,7 +53,7 @@ Component.prototype.setState = function(update, callback) {

const internal = this._internal;
if (update != null && internal) {
if (callback) internal._commitCallbacks.push(callback.bind(this));
if (callback) internal._stateCallbacks.push(callback.bind(this));
internal.rerender(internal);
}
};
Expand Down
4 changes: 4 additions & 0 deletions src/diff/mount.js
Expand Up @@ -367,6 +367,10 @@ function mountComponent(internal, startDom) {
if (renderHook) renderHook(internal);
if (ENABLE_CLASSES && internal.flags & TYPE_CLASS) {
renderResult = c.render(c.props, c.state, c.context);
for (let i = 0; i < internal._stateCallbacks.length; i++) {
internal._commitCallbacks.push(internal._stateCallbacks[i]);
}
internal._stateCallbacks = [];
// note: disable repeat render invocation for class components
break;
} else {
Expand Down
8 changes: 8 additions & 0 deletions src/diff/patch.js
Expand Up @@ -244,6 +244,10 @@ function patchComponent(internal, newVNode) {
c.props = newProps;
c.state = c._nextState;
internal.flags |= SKIP_CHILDREN;
for (let i = 0; i < internal._stateCallbacks.length; i++) {
internal._commitCallbacks.push(internal._stateCallbacks[i]);
}
internal._stateCallbacks = [];
return;
}

Expand All @@ -265,6 +269,10 @@ function patchComponent(internal, newVNode) {
if (renderHook) renderHook(internal);
if (ENABLE_CLASSES && internal.flags & TYPE_CLASS) {
renderResult = c.render(c.props, c.state, c.context);
for (let i = 0; i < internal._stateCallbacks.length; i++) {
internal._commitCallbacks.push(internal._stateCallbacks[i]);
}
internal._stateCallbacks = [];
// note: disable repeat render invocation for class components
break;
} else {
Expand Down
1 change: 1 addition & 0 deletions src/internal.d.ts
Expand Up @@ -164,6 +164,7 @@ export interface Internal<P = {}> {
_depth: number | null;
/** Callbacks to invoke when this internal commits */
_commitCallbacks: Array<() => void>;
_stateCallbacks: Array<() => void>; // Only class components
}

export interface Component<P = {}, S = {}> extends preact.Component<P, S> {
Expand Down
1 change: 1 addition & 0 deletions src/tree.js
Expand Up @@ -89,6 +89,7 @@ export function createInternal(vnode, parentInternal) {
_prevRef: null,
data: flags & TYPE_COMPONENT ? {} : null,
_commitCallbacks: flags & TYPE_COMPONENT ? [] : null,
_stateCallbacks: flags & TYPE_COMPONENT ? [] : null,
rerender: enqueueRender,
flags,
_children: null,
Expand Down
30 changes: 30 additions & 0 deletions test/browser/lifecycles/componentDidMount.test.js
@@ -1,14 +1,17 @@
import { createElement, render, Component } from 'preact';
import { setupRerender } from 'preact/test-utils';
import { setupScratch, teardown } from '../../_util/helpers';

/** @jsx createElement */

describe('Lifecycle methods', () => {
/** @type {HTMLDivElement} */
let scratch;
let rerender;

beforeEach(() => {
scratch = setupScratch();
rerender = setupRerender();
});

afterEach(() => {
Expand All @@ -32,5 +35,32 @@ describe('Lifecycle methods', () => {
render(<App />, scratch);
expect(spy).to.have.been.calledOnceWith(scratch.firstChild);
});

it('supports multiple setState callbacks', () => {
const spy = sinon.spy();

class App extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}

componentDidMount() {
// eslint-disable-next-line
this.setState({ count: 1 }, spy);
// eslint-disable-next-line
this.setState({ count: 2 }, spy);
}

render() {
return <div />;
}
}

render(<App />, scratch);

rerender();
expect(spy).to.have.been.calledTwice;
});
});
});
50 changes: 50 additions & 0 deletions test/browser/lifecycles/componentDidUpdate.test.js
Expand Up @@ -381,5 +381,55 @@ describe('Lifecycle methods', () => {
expect(Inner.prototype.componentDidUpdate).to.have.been.called;
expect(outerChildText).to.equal(`Outer: ${newValue.toString()}`);
});

it('should not interfere with setState callbacks', () => {
let invocation;

class Child extends Component {
componentDidMount() {
this.props.setValue(10);
}
render() {
return <p>Hello world</p>;
}
}

class App extends Component {
constructor(props) {
super(props);
this.state = {
show: false,
count: null
};
}

componentDidMount() {
// eslint-disable-next-line
this.setState({ show: true });
}

componentDidUpdate() {}

render() {
if (this.state.show) {
return (
<Child
setValue={i =>
this.setState({ count: i }, () => {
invocation = this.state;
})
}
/>
);
}
return null;
}
}

render(<App />, scratch);

rerender();
expect(invocation.count).to.equal(10);
});
});
});

0 comments on commit f892739

Please sign in to comment.