Skip to content

Commit 26a4c28

Browse files
committed
backport #3922
1 parent 3abc062 commit 26a4c28

2 files changed

Lines changed: 83 additions & 4 deletions

File tree

hooks/src/index.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,37 @@ export function useReducer(reducer, initialState, init) {
182182
hookState._internal = currentInternal;
183183
if (!currentInternal.data._hasScuFromHooks) {
184184
currentInternal.data._hasScuFromHooks = true;
185-
const prevScu = currentInternal._component.shouldComponentUpdate;
185+
let prevScu = currentInternal._component.shouldComponentUpdate;
186+
const prevCWU = currentInternal._component.componentWillUpdate;
187+
188+
// If we're dealing with a forced update `shouldComponentUpdate` will
189+
// not be called. But we use that to update the hook values, so we
190+
// need to call it.
191+
currentInternal._component.componentWillUpdate = function (p, s, c) {
192+
if (this._force) {
193+
let tmp = prevScu;
194+
// Clear to avoid other sCU hooks from being called
195+
prevScu = undefined;
196+
updateHookState(p, s, c);
197+
prevScu = tmp;
198+
}
199+
200+
if (prevCWU) prevCWU.call(this, p, s, c);
201+
};
186202

187203
// This SCU has the purpose of bailing out after repeated updates
188204
// to stateful hooks.
189205
// we store the next value in _nextValue[0] and keep doing that for all
190206
// state setters, if we have next states and
191207
// all next states within a component end up being equal to their original state
192208
// we are safe to bail out for this specific component.
193-
currentInternal._component.shouldComponentUpdate = function (p, s, c) {
209+
/**
210+
*
211+
* @type {import('./internal').Component["shouldComponentUpdate"]}
212+
*/
213+
// @ts-ignore - We don't use TS to downtranspile
214+
// eslint-disable-next-line no-inner-declarations
215+
function updateHookState(p, s, c) {
194216
if (!hookState._internal.data.__hooks) return true;
195217

196218
const stateHooks = hookState._internal.data.__hooks._list.filter(
@@ -221,7 +243,9 @@ export function useReducer(reducer, initialState, init) {
221243
? prevScu.call(this, p, s, c)
222244
: true
223245
: false;
224-
};
246+
}
247+
248+
currentInternal._component.shouldComponentUpdate = updateHookState;
225249
}
226250
}
227251

hooks/test/browser/combinations.test.js

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import {
77
useEffect,
88
useLayoutEffect,
99
useRef,
10-
useMemo
10+
useMemo,
11+
useContext
1112
} from 'preact/hooks';
1213
import { scheduleEffectAssert } from '../_util/useEffectUtil';
14+
import { createContext } from 'preact';
1315

1416
/** @jsx createElement */
1517

@@ -344,4 +346,57 @@ describe('combinations', () => {
344346

345347
expect(ops).to.deep.equal(['child effect', 'parent effect']);
346348
});
349+
350+
it('should not block hook updates when context updates are enqueued', () => {
351+
const Ctx = createContext({
352+
value: 0,
353+
setValue: /** @type {*} */ () => {}
354+
});
355+
356+
let triggerSubmit = () => {};
357+
function Child() {
358+
const ctx = useContext(Ctx);
359+
const [shouldSubmit, setShouldSubmit] = useState(false);
360+
triggerSubmit = () => setShouldSubmit(true);
361+
362+
useEffect(() => {
363+
if (shouldSubmit) {
364+
// Update parent state and child state at the same time
365+
ctx.setValue(v => v + 1);
366+
setShouldSubmit(false);
367+
}
368+
}, [shouldSubmit]);
369+
370+
return <p>{ctx.value}</p>;
371+
}
372+
373+
function App() {
374+
const [value, setValue] = useState(0);
375+
const ctx = useMemo(() => {
376+
return { value, setValue };
377+
}, [value]);
378+
return (
379+
<Ctx.Provider value={ctx}>
380+
<Child />
381+
</Ctx.Provider>
382+
);
383+
}
384+
385+
act(() => {
386+
render(<App />, scratch);
387+
});
388+
389+
expect(scratch.textContent).to.equal('0');
390+
391+
act(() => {
392+
triggerSubmit();
393+
});
394+
expect(scratch.textContent).to.equal('1');
395+
396+
// This is where the update wasn't applied
397+
act(() => {
398+
triggerSubmit();
399+
});
400+
expect(scratch.textContent).to.equal('2');
401+
});
347402
});

0 commit comments

Comments
 (0)