Skip to content

Commit

Permalink
fix(config): Restore previous original config when ReactQueryConfigPr…
Browse files Browse the repository at this point in the history
…ovider is unmounted (#429)

* restore default config on cleanup

* Add test for nested configs

* Revert "restore default config on cleanup"

This reverts commit c7647a4.

* restore settings

* At least one failing test will pass

* Fix act warning

* Fix other test, copy suspense logic
  • Loading branch information
kamranayub authored May 30, 2020
1 parent 9e0e791 commit f6434fa
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 22 deletions.
53 changes: 34 additions & 19 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,28 @@ import { noop, stableStringify, identity, deepEqual } from './utils'

export const configContext = React.createContext()

const DEFAULTS = {
retry: 3,
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
staleTime: 0,
cacheTime: 5 * 60 * 1000,
refetchAllOnWindowFocus: true,
refetchInterval: false,
suspense: false,
queryKeySerializerFn: defaultQueryKeySerializerFn,
queryFnParamsFilter: identity,
throwOnError: false,
useErrorBoundary: undefined, // this will default to the suspense value
onMutate: noop,
onSuccess: noop,
onError: noop,
onSettled: noop,
refetchOnMount: true,
isDataEqual: deepEqual,
}

export const defaultConfigRef = {
current: {
retry: 3,
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
staleTime: 0,
cacheTime: 5 * 60 * 1000,
refetchAllOnWindowFocus: true,
refetchInterval: false,
suspense: false,
queryKeySerializerFn: defaultQueryKeySerializerFn,
queryFnParamsFilter: identity,
throwOnError: false,
useErrorBoundary: undefined, // this will default to the suspense value
onMutate: noop,
onSuccess: noop,
onError: noop,
onSettled: noop,
refetchOnMount: true,
isDataEqual: deepEqual,
},
current: DEFAULTS,
}

export function useConfigContext() {
Expand All @@ -46,6 +48,19 @@ export function ReactQueryConfigProvider({ config, children }) {
return newConfig
}, [config, configContextValue])

React.useEffect(() => {
// restore previous config on unmount
return () => {
defaultConfigRef.current = { ...(configContextValue || DEFAULTS) }

// Default useErrorBoundary to the suspense value
if (typeof defaultConfigRef.current.useErrorBoundary === 'undefined') {
defaultConfigRef.current.useErrorBoundary =
defaultConfigRef.current.suspense
}
}
}, [configContextValue])

if (!configContextValue) {
defaultConfigRef.current = newConfig
}
Expand Down
156 changes: 153 additions & 3 deletions src/tests/config.test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import React from 'react'
import { render, waitForElement, cleanup } from '@testing-library/react'
import React, { useState } from 'react'
import {
act,
fireEvent,
render,
waitForElement,
cleanup,
} from '@testing-library/react'
import {
ReactQueryConfigProvider,
useQuery,
queryCache,
ReactQueryCacheProvider,
} from '../index'

import { sleep } from './utils'

describe('config', () => {
afterEach(() => {
cleanup()
queryCache.clear()
})

// See https://github.com/tannerlinsley/react-query/issues/105
Expand Down Expand Up @@ -46,4 +53,147 @@ describe('config', () => {

expect(onSuccess).toHaveBeenCalledWith('data')
})

it('should reset to defaults when all providers are unmounted', async () => {
const onSuccess = jest.fn()

const config = {
refetchAllOnWindowFocus: false,
refetchOnMount: false,
retry: false,
manual: true,
}

const queryFn = async () => {
await sleep(10)
return 'data'
}

function Page() {
const { data } = useQuery('test', queryFn)

return (
<div>
<h1>Data: {data || 'none'}</h1>
</div>
)
}

const rendered = render(
<ReactQueryConfigProvider config={config}>
<Page />
</ReactQueryConfigProvider>
)

await rendered.findByText('Data: none')

act(() => {
queryCache.prefetchQuery('test', queryFn, { force: true })
})

await rendered.findByText('Data: data')

// tear down and unmount
cleanup()

// wipe query cache/stored config
act(() => queryCache.clear())
onSuccess.mockClear()

// rerender WITHOUT config provider,
// so we are NOT passing the config above (refetchOnMount should be `true` by default)
const rerendered = render(<Page />)

await rerendered.findByText('Data: data')
})

it('should reset to previous config when nested provider is unmounted', async () => {
let counterRef = 0
const parentOnSuccess = jest.fn()

const parentConfig = {
refetchOnMount: false,
onSuccess: parentOnSuccess,
}

const childConfig = {
refetchOnMount: true,

// Override onSuccess of parent, making it a no-op
onSuccess: undefined,
}

const queryFn = async () => {
await sleep(10)
counterRef += 1
return String(counterRef)
}

function Component() {
const { data, refetch } = useQuery('test', queryFn)

return (
<div>
<h1>Data: {data}</h1>
<button data-testid="refetch" onClick={() => refetch()}>
Refetch
</button>
</div>
)
}

function Page() {
const [childConfigEnabled, setChildConfigEnabled] = useState(true)

return (
<div>
{childConfigEnabled && (
<ReactQueryConfigProvider config={childConfig}>
<Component />
</ReactQueryConfigProvider>
)}
{!childConfigEnabled && <Component />}
<button
data-testid="disableChildConfig"
onClick={() => setChildConfigEnabled(false)}
>
Disable Child Config
</button>
</div>
)
}

const rendered = render(
<ReactQueryConfigProvider config={parentConfig}>
<ReactQueryCacheProvider>
<Page />
</ReactQueryCacheProvider>
</ReactQueryConfigProvider>
)

await rendered.findByText('Data: 1')

expect(parentOnSuccess).not.toHaveBeenCalled()

fireEvent.click(rendered.getByTestId('refetch'))

await rendered.findByText('Data: 2')

expect(parentOnSuccess).not.toHaveBeenCalled()

parentOnSuccess.mockReset()

fireEvent.click(rendered.getByTestId('disableChildConfig'))

await rendered.findByText('Data: 2')

// it should not refetch on mount
expect(parentOnSuccess).not.toHaveBeenCalled()

fireEvent.click(rendered.getByTestId('refetch'))

await rendered.findByText('Data: 3')

expect(parentOnSuccess).toHaveBeenCalledTimes(1)
})
})

0 comments on commit f6434fa

Please sign in to comment.