Skip to content

Commit

Permalink
Merge pull request #3621 from preactjs/defer-state-equality
Browse files Browse the repository at this point in the history
defer bailing out of updates to the render phase
  • Loading branch information
marvinhagemeister committed Jul 19, 2022
2 parents cef315a + a1fc94f commit 3d0cdf7
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 7 deletions.
31 changes: 26 additions & 5 deletions hooks/src/index.js
Expand Up @@ -43,8 +43,11 @@ options._render = vnode => {
hooks._pendingEffects = [];
currentComponent._renderCallbacks = [];
hooks._list.forEach(hookItem => {
if (hookItem._nextValue) {
hookItem._value = hookItem._nextValue;
}
hookItem._pendingValue = EMPTY;
hookItem._pendingArgs = undefined;
hookItem._nextValue = hookItem._pendingArgs = undefined;
});
} else {
hooks._pendingEffects.forEach(invokeCleanup);
Expand Down Expand Up @@ -164,18 +167,35 @@ export function useReducer(reducer, initialState, init) {
!init ? invokeOrReturn(undefined, initialState) : init(initialState),

action => {
const nextValue = hookState._reducer(hookState._value[0], action);
if (hookState._value[0] !== nextValue) {
hookState._value = [nextValue, hookState._value[1]];
const currentValue = hookState._nextValue
? hookState._nextValue[0]
: hookState._value[0];
const nextValue = hookState._reducer(currentValue, action);

if (currentValue !== nextValue) {
hookState._nextValue = [
hookState._reducer(currentValue, action),
hookState._value[1]
];
hookState._component.setState({});
}
}
];

hookState._component = currentComponent;

hookState._component.shouldComponentUpdate = () => {
if (!hookState._nextValue) return true;

const currentValue = hookState._value[0];
hookState._value = hookState._nextValue;
hookState._nextValue = undefined;

return currentValue !== hookState._value[0];
};
}

return hookState._value;
return hookState._nextValue || hookState._value;
}

/**
Expand Down Expand Up @@ -385,6 +405,7 @@ function invokeCleanup(hook) {
hook._cleanup = undefined;
cleanup();
}

currentComponent = comp;
}

Expand Down
1 change: 1 addition & 0 deletions hooks/src/internal.d.ts
Expand Up @@ -62,6 +62,7 @@ export interface MemoHookState {
}

export interface ReducerHookState {
_nextValue?: any;
_value?: any;
_component?: Component;
_reducer?: Reducer<any, any>;
Expand Down
56 changes: 54 additions & 2 deletions hooks/test/browser/useState.test.js
@@ -1,7 +1,7 @@
import { setupRerender, act } from 'preact/test-utils';
import { createElement, render } from 'preact';
import { createElement, render, createContext } from 'preact';
import { useState, useContext, useEffect } from 'preact/hooks';
import { setupScratch, teardown } from '../../../test/_util/helpers';
import { useState } from 'preact/hooks';

/** @jsx createElement */

Expand Down Expand Up @@ -233,4 +233,56 @@ describe('useState', () => {
expect(calls).to.deep.equal(['bye', 'hi']);
expect(scratch.textContent).to.equal('hi');
});

it('does not loop when states are equal after batches', () => {
const renderSpy = sinon.spy();
const Context = createContext(null);

function ModalProvider(props) {
let [modalCount, setModalCount] = useState(0);
renderSpy(modalCount);
let context = {
modalCount,
addModal() {
setModalCount(count => count + 1);
},
removeModal() {
setModalCount(count => count - 1);
}
};

return (
<Context.Provider value={context}>{props.children}</Context.Provider>
);
}

function useModal() {
let context = useContext(Context);
useEffect(() => {
context.addModal();
return () => {
context.removeModal();
};
}, [context]);
}

function Popover() {
useModal();
return <div>Popover</div>;
}

function App() {
return (
<ModalProvider>
<Popover />
</ModalProvider>
);
}

act(() => {
render(<App />, scratch);
});

expect(renderSpy).to.be.calledTwice;
});
});
1 change: 1 addition & 0 deletions mangle.json
Expand Up @@ -31,6 +31,7 @@
"$_pendingEffects": "__h",
"$_value": "__",
"$_pendingValue": "__V",
"$_nextValue": "__N",
"$_original": "__v",
"$_args": "__H",
"$_factory": "__h",
Expand Down

0 comments on commit 3d0cdf7

Please sign in to comment.