From ab79819fd41dc89ce63db6986dd78cd10dec0894 Mon Sep 17 00:00:00 2001 From: Dor Shtaif Date: Fri, 27 Dec 2024 22:19:05 +0200 Subject: [PATCH] fix yielding consecutive identical values causes unnecessary re-renders for `useAsyncIter` and `` in misalignment with `React.useState` --- spec/tests/Iterate.spec.tsx | 36 +++++++++++++++++++++++++++++++++ spec/tests/useAsyncIter.spec.ts | 27 +++++++++++++++++++++++++ src/useAsyncIter/index.ts | 16 ++++++++------- 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/spec/tests/Iterate.spec.tsx b/spec/tests/Iterate.spec.tsx index 432a9d1..cc4f67c 100644 --- a/spec/tests/Iterate.spec.tsx +++ b/spec/tests/Iterate.spec.tsx @@ -667,6 +667,42 @@ describe('`Iterate` component', () => { ); } ); + + it( + gray( + 'When given iterable yields consecutive identical values the hook will not consequently re-render' + ), + async () => { + let timesRerendered = 0; + let lastRenderFnInput: undefined | IterationResult; + const channel = new IterableChannelTestHelper(); + + const rendered = render( + + {next => { + timesRerendered++; + lastRenderFnInput = next; + return
Render count: {timesRerendered}
; + }} +
+ ); + + for (let i = 0; i < 3; ++i) { + await act(() => channel.put('a')); + } + + expect(timesRerendered).toStrictEqual(2); + expect(lastRenderFnInput).toStrictEqual({ + value: 'a', + pendingFirst: false, + done: false, + error: undefined, + }); + expect(rendered.container.innerHTML).toStrictEqual( + '
Render count: 2
' + ); + } + ); }); const simulatedError = new Error('🚨 Simulated Error 🚨'); diff --git a/spec/tests/useAsyncIter.spec.ts b/spec/tests/useAsyncIter.spec.ts index 5243b04..a4fe16a 100644 --- a/spec/tests/useAsyncIter.spec.ts +++ b/spec/tests/useAsyncIter.spec.ts @@ -471,6 +471,33 @@ describe('`useAsyncIter` hook', () => { }); } ); + + it( + gray( + 'When given iterable yields consecutive identical values the hook will not consequently re-render' + ), + async () => { + let timesRerendered = 0; + const channel = new IterableChannelTestHelper(); + + const renderedHook = renderHook(() => { + timesRerendered++; + return useAsyncIter(channel); + }); + + for (let i = 0; i < 3; ++i) { + await act(() => channel.put('a')); + } + + expect(timesRerendered).toStrictEqual(2); + expect(renderedHook.result.current).toStrictEqual({ + value: 'a', + pendingFirst: false, + done: false, + error: undefined, + }); + } + ); }); const simulatedError = new Error('🚨 Simulated Error 🚨'); diff --git a/src/useAsyncIter/index.ts b/src/useAsyncIter/index.ts index a93fcb1..7fba0cf 100644 --- a/src/useAsyncIter/index.ts +++ b/src/useAsyncIter/index.ts @@ -169,13 +169,15 @@ const useAsyncIter: { iterationIdx++ ) ?? (value as ExtractAsyncIterValue); - stateRef.current = { - value: formattedValue, - pendingFirst: false, - done: false, - error: undefined, - }; - rerender(); + if (!Object.is(formattedValue, stateRef.current.value)) { + stateRef.current = { + value: formattedValue, + pendingFirst: false, + done: false, + error: undefined, + }; + rerender(); + } } } if (!iteratorClosedByConsumer) {