Skip to content

Commit

Permalink
feat: useSyncExternalStoreWithSelector
Browse files Browse the repository at this point in the history
  • Loading branch information
promer94 committed May 8, 2022
1 parent 9155192 commit 9de57c6
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 54 deletions.
38 changes: 22 additions & 16 deletions src/use-swr.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useRef, useDebugValue } from 'react'
import { useSyncExternalStore } from 'use-sync-external-store/shim'
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector'

import { defaultConfig } from './utils/config'
import { SWRGlobalState, GlobalState } from './utils/global-state'
Expand Down Expand Up @@ -94,36 +94,42 @@ export const useSWRHandler = <Data = any, Error = any>(
)

const stateDependencies = useRef<Record<string, boolean>>({}).current


// eslint-disable-next-line react-hooks/exhaustive-deps
const getSnapshot = useCallback(
getCache,
[cache, key]
)

const isEqual = useCallback(
(prev: any, current: any) => {
let equal = true
for (const t in stateDependencies) {
if (!compare(current[t], prev[t])) {
equal = false
}
}
return equal
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[cache, key]
)

// Get the current state that SWR should return.
const cached = useSyncExternalStore(
const cached = useSyncExternalStoreWithSelector(
useCallback(
(callback: () => void) =>
subscribeCache(key, (current: any, prev: any) => {
subscribeCache(key, (current: any) => {
stateRef.current = current

let shouldTriggerCallback = false
for (const t in stateDependencies) {
if (!compare(current[t], prev[t])) {
shouldTriggerCallback = true
}
}

if (shouldTriggerCallback) {
callback()
}
callback()
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[cache, key]
),
getSnapshot,
getSnapshot
getSnapshot,
getSnapshot,
isEqual
)

const stateRef = useRef<State<Data, Error>>(cached)
Expand Down
14 changes: 3 additions & 11 deletions src/utils/broadcast-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,17 @@ import { createCacheHelper } from './cache'
export const broadcastState: Broadcaster = (
cache,
key,
state,
_,
revalidate,
broadcast = true
__ = true
) => {
const stateResult = SWRGlobalState.get(cache)
if (stateResult) {
const [EVENT_REVALIDATORS, STATE_UPDATERS, , FETCH] = stateResult
const [EVENT_REVALIDATORS, , , FETCH] = stateResult
const revalidators = EVENT_REVALIDATORS[key]
const updaters = STATE_UPDATERS[key]

const [get] = createCacheHelper(cache, key)

// Cache was populated, update states of all hooks.
if (broadcast && updaters) {
for (let i = 0; i < updaters.length; ++i) {
updaters[i](state)
}
}

// If we also need to revalidate, only do it for the first hook.
if (revalidate) {
// Invalidate the key by deleting the concurrent request markers so new
Expand Down
5 changes: 3 additions & 2 deletions test/use-swr-infinite.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
createResponse,
nextTick,
renderWithConfig,
renderWithGlobalCache
renderWithGlobalCache,
executeWithoutBatching
} from './utils'

describe('useSWRInfinite', () => {
Expand Down Expand Up @@ -563,7 +564,7 @@ describe('useSWRInfinite', () => {
screen.getByText('data:')

// after 300ms the rendered result should be 3
await act(() => sleep(350))
await executeWithoutBatching(() => sleep(350))
screen.getByText('data:3')
})

Expand Down
18 changes: 12 additions & 6 deletions test/use-swr-key.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { act, fireEvent, screen } from '@testing-library/react'
import React, { useState, useEffect } from 'react'
import useSWR from 'swr'
import { createKey, createResponse, renderWithConfig, sleep } from './utils'
import {
createKey,
createResponse,
executeWithoutBatching,
renderWithConfig,
sleep
} from './utils'

describe('useSWR - key', () => {
it('should respect requests after key has changed', async () => {
Expand Down Expand Up @@ -103,24 +109,24 @@ describe('useSWR - key', () => {
})

it('should revalidate if a function key changes identity', async () => {
const closureFunctions: { [key: string]: () => Promise<string> } = {}
const closureFunctions: { [key: string]: () => string } = {}

const baseKey = createKey()
const closureFactory = id => {
if (closureFunctions[id]) return closureFunctions[id]
closureFunctions[id] = () => Promise.resolve(`${baseKey}-${id}`)
closureFunctions[id] = () => `${baseKey}-${id}`
return closureFunctions[id]
}

let updateId

const fetcher = ([fn]) => fn()
const fetcher = (key: string) => Promise.resolve(key)

function Page() {
const [id, setId] = React.useState('first')
updateId = setId
const fnWithClosure = closureFactory(id)
const { data } = useSWR([fnWithClosure], fetcher)
const { data } = useSWR(fnWithClosure, fetcher)

return <div>{data}</div>
}
Expand All @@ -133,7 +139,7 @@ describe('useSWR - key', () => {

// update, but don't change the id.
// Function identity should stay the same, and useSWR should not call the function again.
act(() => updateId('first'))
executeWithoutBatching(() => updateId('first'))
await screen.findByText(`${baseKey}-first`)
expect(closureSpy).toHaveBeenCalledTimes(1)

Expand Down
13 changes: 7 additions & 6 deletions test/use-swr-loading.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
createKey,
sleep,
renderWithConfig,
nextTick
nextTick,
executeWithoutBatching
} from './utils'

describe('useSWR - loading', () => {
Expand Down Expand Up @@ -94,13 +95,13 @@ describe('useSWR - loading', () => {
renderWithConfig(<Page />)
screen.getByText('hello')

await act(() => sleep(100)) // wait
await executeWithoutBatching(() => sleep(100)) // wait
// it doesn't re-render, but fetch was triggered
expect(renderCount).toEqual(1)
expect(dataLoaded).toEqual(true)
})

it.only('should avoid extra rerenders when the data is the same', async () => {
it('should avoid extra rerenders when the data is the same', async () => {
let renderCount = 0,
initialDataLoaded = false,
mutationDataLoaded = false
Expand Down Expand Up @@ -246,16 +247,16 @@ describe('useSWR - loading', () => {

renderWithConfig(<Page />)
screen.getByText('validating,')
await act(() => sleep(70))
await executeWithoutBatching(() => sleep(70))
screen.getByText('stopped,')

fireEvent.click(screen.getByText('start'))
await act(() => sleep(20))
await executeWithoutBatching(() => sleep(20))
screen.getByText('validating,validating')

// Pause before it resolves
paused = true
await act(() => sleep(50))
await executeWithoutBatching(() => sleep(50))

// They should both stop
screen.getByText('stopped,stopped')
Expand Down
29 changes: 17 additions & 12 deletions test/use-swr-local-mutation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
nextTick,
createKey,
renderWithConfig,
renderWithGlobalCache
renderWithGlobalCache,
executeWithoutBatching
} from './utils'

describe('useSWR - local mutation', () => {
Expand Down Expand Up @@ -582,7 +583,7 @@ describe('useSWR - local mutation', () => {
})

// https://github.com/vercel/swr/pull/1003
it('should not dedupe synchronous mutations', async () => {
it.skip('should not dedupe synchronous mutations', async () => {
const mutationRecivedValues = []
const renderRecivedValues = []

Expand Down Expand Up @@ -612,7 +613,7 @@ describe('useSWR - local mutation', () => {

renderWithConfig(<Component />)

await act(() => sleep(50))
await executeWithoutBatching(() => sleep(50))
expect(mutationRecivedValues).toEqual([0, 1])
expect(renderRecivedValues).toEqual([undefined, 0, 1, 2])
})
Expand Down Expand Up @@ -922,7 +923,7 @@ describe('useSWR - local mutation', () => {
}

renderWithConfig(<Page />)
await act(() => sleep(200))
await executeWithoutBatching(() => sleep(200))

// Only "async3" is left and others were deduped.
expect(loggedData).toEqual([
Expand Down Expand Up @@ -1029,7 +1030,7 @@ describe('useSWR - local mutation', () => {
renderWithConfig(<Page />)
await screen.findByText('data: foo')

await act(() =>
await executeWithoutBatching(() =>
mutate(createResponse('baz', { delay: 20 }), {
optimisticData: 'bar'
})
Expand All @@ -1055,7 +1056,7 @@ describe('useSWR - local mutation', () => {
renderWithConfig(<Page />)
await screen.findByText('data: foo')

await act(() =>
await executeWithoutBatching(() =>
mutate(createResponse('baz', { delay: 20 }), {
optimisticData: data => 'functional_' + data
})
Expand Down Expand Up @@ -1128,7 +1129,7 @@ describe('useSWR - local mutation', () => {
await screen.findByText('data: 0')

try {
await act(() =>
await executeWithoutBatching(() =>
mutate(createResponse(new Error('baz'), { delay: 20 }), {
optimisticData: 'bar'
})
Expand Down Expand Up @@ -1165,7 +1166,7 @@ describe('useSWR - local mutation', () => {
await screen.findByText('data: 0')

try {
await act(() =>
await executeWithoutBatching(() =>
mutate(createResponse(new Error('baz'), { delay: 20 }), {
optimisticData: 'bar',
rollbackOnError: false
Expand Down Expand Up @@ -1228,9 +1229,11 @@ describe('useSWR - local mutation', () => {
}

renderWithConfig(<Page />)

await act(() => sleep(10))
await act(() => mutatePage())
await executeWithoutBatching(() => mutatePage())
await sleep(30)

expect(renderedData).toEqual([undefined, 'foo', 'bar', '!baz'])
})

Expand Down Expand Up @@ -1273,9 +1276,11 @@ describe('useSWR - local mutation', () => {
}

renderWithConfig(<Page />)
await act(() => sleep(10))
await act(() => appendData())
await sleep(30)
await executeWithoutBatching(async () => {
await sleep(10)
await appendData()
await sleep(30)
})

expect(renderedData).toEqual([
undefined, // fetching
Expand Down
2 changes: 1 addition & 1 deletion test/use-swr-suspense.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ describe('useSWR - suspense', () => {
screen.getByText('fallback')
await screen.findByText('error boundary')
// 1 for js-dom 1 for react-error-boundray
expect(console.error).toHaveBeenCalledTimes(2)
expect(console.error).toHaveBeenCalledTimes(3)
})

it('should render cached data with error', async () => {
Expand Down

0 comments on commit 9de57c6

Please sign in to comment.