From 34d328f0c016c1cb864d98f04473128fc4e713ec Mon Sep 17 00:00:00 2001 From: Martin Hansen Date: Wed, 20 Sep 2023 15:43:29 +0200 Subject: [PATCH 1/2] Add unit test for bug with setting object/array in state in useEffect hook --- packages/core/core.test.mts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/core/core.test.mts b/packages/core/core.test.mts index d68531537..503652124 100644 --- a/packages/core/core.test.mts +++ b/packages/core/core.test.mts @@ -83,6 +83,33 @@ describe('createPrompt()', () => { await expect(answer).resolves.toEqual('up'); }); + it('useEffect: works with setting state at once with objects', async () => { + const Prompt = (config: { message: string }, done: (value: string) => void) => { + const [value, setValue] = useState([1, 2]); + + useEffect(() => { + setValue([1, 3]); + }, []); + + useKeypress((key: KeypressEvent) => { + if (isEnterKey(key)) { + done(String(value)); + } + }); + + return String(value); + }; + + const prompt = createPrompt(Prompt); + const { answer, events } = await render(prompt, { message: 'Question' }); + events.keypress('enter'); + + // awaiting it instead of using await expect(answer).resolves.toEqual('1,3') + // as this produces a better error message. + const resolvedAnswer = await answer; + expect(resolvedAnswer).toEqual('1,3'); + }); + it('useEffect: re-run only on change', async () => { const effect = vi.fn(); const effectCleanup = vi.fn(); From 835f0702d7a4310146c28bb484694320ac80f51a Mon Sep 17 00:00:00 2001 From: Simon Boudrias Date: Mon, 25 Sep 2023 18:02:51 -0400 Subject: [PATCH 2/2] Bugfix (core): Fix infinite loop happening if state change occured directly inside useEffect() --- packages/core/src/lib/hook-engine.mts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core/src/lib/hook-engine.mts b/packages/core/src/lib/hook-engine.mts index 5f6c41527..acd9417c4 100644 --- a/packages/core/src/lib/hook-engine.mts +++ b/packages/core/src/lib/hook-engine.mts @@ -121,7 +121,9 @@ export const effectScheduler = { store.hooksEffect.forEach((effect) => { effect(); }); + // Warning: Clean the hooks before exiting the `withUpdates` block. + // Failure to do so means an updates would hit the same effects again. + store.hooksEffect.length = 0; })(); - store.hooksEffect.length = 0; }, };