diff --git a/src/use-swr.ts b/src/use-swr.ts index daf237aa9..dac75fcbc 100644 --- a/src/use-swr.ts +++ b/src/use-swr.ts @@ -263,6 +263,12 @@ export const useSWRHandler = ( // For local state, compare and assign. if (!compare(stateRef.current.data, newData)) { newState.data = newData + } else { + // data and newData are deeply equal + // it should be safe to broadcast the stale data + newState.data = stateRef.current.data + // At the end of this function, `brocastState` invokes the `onStateUpdate` function, + // which takes care of avoiding the re-render } // For global state, it's possible that the key has changed. diff --git a/test/use-swr-loading.test.tsx b/test/use-swr-loading.test.tsx index b5618f2d2..8d47bfa98 100644 --- a/test/use-swr-loading.test.tsx +++ b/test/use-swr-loading.test.tsx @@ -1,5 +1,5 @@ import { act, screen, fireEvent } from '@testing-library/react' -import React from 'react' +import React, { useEffect } from 'react' import useSWR from 'swr' import { createResponse, @@ -77,6 +77,51 @@ describe('useSWR - loading', () => { expect(dataLoaded).toEqual(true) }) + it('should avoid extra rerenders is the data is the `same`', async () => { + let renderCount = 0, + initialDataLoaded = false, + mutationDataLoaded = false + + const key = createKey() + function Page() { + const { data, mutate } = useSWR( + key, + async () => { + const res = await createResponse({ greeting: 'hello' }) + initialDataLoaded = true + return res + }, + { fallbackData: { greeting: 'hello' } } + ) + + useEffect(() => { + const timeout = setTimeout( + () => + mutate(async () => { + const res = await createResponse({ greeting: 'hello' }) + mutationDataLoaded = true + return res + }), + 200 + ) + + return () => clearTimeout(timeout) + }, [mutate]) + + renderCount++ + return
{data?.greeting}
+ } + + renderWithConfig() + screen.getByText('hello') + + await act(() => sleep(1000)) // wait + // it doesn't re-render, but fetch was triggered + expect(initialDataLoaded).toEqual(true) + expect(mutationDataLoaded).toEqual(true) + expect(renderCount).toEqual(1) + }) + it('should return enumerable object', async () => { // If the returned object is enumerable, we can use the spread operator // to deconstruct all the keys.