Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: remove contextSharing #4723

Merged
merged 18 commits into from Jan 8, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/react/guides/migrating-to-react-query-5.md
Expand Up @@ -26,3 +26,9 @@ if you still need to remove a query, you can use `queryClient.removeQueries({que
### The minimum required TypeScript version is now 4.7

Mainly because an important fix was shipped around type inference. Please see this [TypeScript issue](https://github.com/microsoft/TypeScript/issues/43371) for more information.

### The `contextSharing` prop has been removed from QueryClientProvider

You could previously use the `contextSharing` property to share the first (and at least one) instance of the query client context across the window. This ensured that if TanStack Query was used across different bundles or microfrontends then they will all use the same instance of the context, regardless of module scoping.

However, isolation is often preferred for microfrontends. In v4 the option to pass a custom context to the `QueryClientProvider` was added, which allows exactly this. If you wish to use the same query client across multiple packages of an application, you can create a `QueryClient` in your application and then let the bundles share this through the `context` property of the `QueryClientProvider`.
TkDodo marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 0 additions & 4 deletions docs/react/reference/QueryClientProvider.md
Expand Up @@ -20,9 +20,5 @@ function App() {
- `client: QueryClient`
- **Required**
- the QueryClient instance to provide
- `contextSharing: boolean`
- **Deprecated**
- defaults to `false`
- Set this to `true` to enable context sharing, which will share the first and at least one instance of the context across the window to ensure that if React Query is used across different bundles or microfrontends they will all use the same **instance** of context, regardless of module scoping.
- `context?: React.Context<QueryClient | undefined>`
- Use this to use a custom React Query context. Otherwise, `defaultContext` will be used.
58 changes: 7 additions & 51 deletions packages/react-query/src/QueryClientProvider.tsx
Expand Up @@ -3,46 +3,22 @@ import * as React from 'react'
import type { QueryClient } from '@tanstack/query-core'
import type { ContextOptions } from './types'

declare global {
interface Window {
ReactQueryClientContext?: React.Context<QueryClient | undefined>
}
}

export const defaultContext = React.createContext<QueryClient | undefined>(
undefined,
)
const QueryClientSharingContext = React.createContext<boolean>(false)

// If we are given a context, we will use it.
// Otherwise, if contextSharing is on, we share the first and at least one
// instance of the context across the window
// to ensure that if React Query is used across
// different bundles or microfrontends they will
// all use the same **instance** of context, regardless
// of module scoping.
function getQueryClientContext(
context: React.Context<QueryClient | undefined> | undefined,
TkDodo marked this conversation as resolved.
Show resolved Hide resolved
contextSharing: boolean,
) {
if (context) {
return context
}
if (contextSharing && typeof window !== 'undefined') {
if (!window.ReactQueryClientContext) {
window.ReactQueryClientContext = defaultContext
}

return window.ReactQueryClientContext
}

return defaultContext
TkDodo marked this conversation as resolved.
Show resolved Hide resolved
}

export const useQueryClient = ({ context }: ContextOptions = {}) => {
const queryClient = React.useContext(
getQueryClientContext(context, React.useContext(QueryClientSharingContext)),
)
const queryClient = React.useContext(getQueryClientContext(context))

if (!queryClient) {
throw new Error('No QueryClient set, use QueryClientProvider to set one')
Expand All @@ -51,27 +27,15 @@ export const useQueryClient = ({ context }: ContextOptions = {}) => {
return queryClient
}

type QueryClientProviderPropsBase = {
export type QueryClientProviderProps = {
client: QueryClient
children?: React.ReactNode
}
type QueryClientProviderPropsWithContext = ContextOptions & {
contextSharing?: never
} & QueryClientProviderPropsBase
type QueryClientProviderPropsWithContextSharing = {
context?: never
contextSharing?: boolean
} & QueryClientProviderPropsBase

export type QueryClientProviderProps =
| QueryClientProviderPropsWithContext
| QueryClientProviderPropsWithContextSharing
} & ContextOptions

export const QueryClientProvider = ({
client,
children,
context,
contextSharing = false,
}: QueryClientProviderProps): JSX.Element => {
React.useEffect(() => {
client.mount()
Expand All @@ -80,19 +44,11 @@ export const QueryClientProvider = ({
}
}, [client])

if (process.env.NODE_ENV !== 'production' && contextSharing) {
client
.getLogger()
.error(
`The contextSharing option has been deprecated and will be removed in the next major version`,
)
}

const Context = getQueryClientContext(context, contextSharing)
const QueryClientContext = getQueryClientContext(context)

return (
<QueryClientSharingContext.Provider value={!context && contextSharing}>
<Context.Provider value={client}>{children}</Context.Provider>
</QueryClientSharingContext.Provider>
<QueryClientContext.Provider value={client}>
{children}
</QueryClientContext.Provider>
)
}
63 changes: 1 addition & 62 deletions packages/react-query/src/__tests__/QueryClientProvider.test.tsx
@@ -1,6 +1,5 @@
import * as React from 'react'
import { render, waitFor } from '@testing-library/react'
import { renderToString } from 'react-dom/server'

import { sleep, queryKey, createQueryClient } from './utils'
import {
Expand Down Expand Up @@ -178,16 +177,10 @@ describe('QueryClientProvider', () => {
)
}

// contextSharing should be ignored when passing a custom context.
const contextSharing = true

const rendered = render(
<QueryClientProvider client={queryClientOuter} context={contextOuter}>
<QueryClientProvider client={queryClientInner} context={contextInner}>
<QueryClientProvider
client={queryClientInnerInner}
contextSharing={contextSharing}
>
<QueryClientProvider client={queryClientInnerInner}>
<Page />
</QueryClientProvider>
</QueryClientProvider>
Expand Down Expand Up @@ -217,59 +210,5 @@ describe('QueryClientProvider', () => {

consoleMock.mockRestore()
})

test('should use window to get the context when contextSharing is true', () => {
const queryCache = new QueryCache()
const queryClient = createQueryClient({ queryCache })

let queryClientFromHook: QueryClient | undefined
let queryClientFromWindow: QueryClient | undefined

function Page() {
queryClientFromHook = useQueryClient()
queryClientFromWindow = React.useContext(
window.ReactQueryClientContext as React.Context<
QueryClient | undefined
>,
)
return null
}

render(
<QueryClientProvider client={queryClient} contextSharing={true}>
<Page />
</QueryClientProvider>,
)

expect(queryClientFromHook).toEqual(queryClient)
expect(queryClientFromWindow).toEqual(queryClient)
})

test('should not use window to get the context when contextSharing is true and window does not exist', () => {
const queryCache = new QueryCache()
const queryClient = createQueryClient({ queryCache })

// Mock a non web browser environment
const windowSpy = jest
.spyOn(window, 'window', 'get')
.mockImplementation(undefined)

let queryClientFromHook: QueryClient | undefined

function Page() {
queryClientFromHook = useQueryClient()
return null
}

renderToString(
<QueryClientProvider client={queryClient} contextSharing={true}>
<Page />
</QueryClientProvider>,
)

expect(queryClientFromHook).toEqual(queryClient)

windowSpy.mockRestore()
})
})
})
82 changes: 10 additions & 72 deletions packages/solid-query/src/QueryClientProvider.tsx
@@ -1,52 +1,22 @@
import type { QueryClient } from '@tanstack/query-core'
import type { Context, JSX } from 'solid-js'
import {
createContext,
useContext,
onMount,
onCleanup,
mergeProps,
} from 'solid-js'
import { createContext, useContext, onMount, onCleanup } from 'solid-js'
import type { ContextOptions } from './types'

declare global {
interface Window {
SolidQueryClientContext?: Context<QueryClient | undefined>
}
}

export const defaultContext = createContext<QueryClient | undefined>(undefined)
const QueryClientSharingContext = createContext<boolean>(false)

// If we are given a context, we will use it.
// Otherwise, if contextSharing is on, we share the first and at least one
// instance of the context across the window
// to ensure that if Solid Query is used across
// different bundles or microfrontends they will
// all use the same **instance** of context, regardless
// of module scoping.
function getQueryClientContext(
context: Context<QueryClient | undefined> | undefined,
contextSharing: boolean,
) {
if (context) {
return context
}
if (contextSharing && typeof window !== 'undefined') {
if (!window.SolidQueryClientContext) {
window.SolidQueryClientContext = defaultContext
}

return window.SolidQueryClientContext
}

return defaultContext
}

export const useQueryClient = ({ context }: ContextOptions = {}) => {
const queryClient = useContext(
getQueryClientContext(context, useContext(QueryClientSharingContext)),
)
const queryClient = useContext(getQueryClientContext(context))

if (!queryClient) {
throw new Error('No QueryClient set, use QueryClientProvider to set one')
Expand All @@ -55,56 +25,24 @@ export const useQueryClient = ({ context }: ContextOptions = {}) => {
return queryClient
}

type QueryClientProviderPropsBase = {
export type QueryClientProviderProps = {
client: QueryClient
children?: JSX.Element
}
type QueryClientProviderPropsWithContext = ContextOptions & {
contextSharing?: never
} & QueryClientProviderPropsBase
type QueryClientProviderPropsWithContextSharing = {
context?: never
contextSharing?: boolean
} & QueryClientProviderPropsBase

export type QueryClientProviderProps =
| QueryClientProviderPropsWithContext
| QueryClientProviderPropsWithContextSharing
} & ContextOptions

export const QueryClientProvider = (
props: QueryClientProviderProps,
): JSX.Element => {
const mergedProps = mergeProps(
{
contextSharing: false,
},
props,
)
onMount(() => {
mergedProps.client.mount()

if (process.env.NODE_ENV !== 'production' && mergedProps.contextSharing) {
mergedProps.client
.getLogger()
.error(
`The contextSharing option has been deprecated and will be removed in the next major version`,
)
}
props.client.mount()
})
onCleanup(() => mergedProps.client.unmount())
onCleanup(() => props.client.unmount())

const QueryClientContext = getQueryClientContext(
mergedProps.context,
mergedProps.contextSharing,
)
const QueryClientContext = getQueryClientContext(props.context)

return (
<QueryClientSharingContext.Provider
value={!mergedProps.context && mergedProps.contextSharing}
>
<QueryClientContext.Provider value={mergedProps.client}>
{mergedProps.children}
</QueryClientContext.Provider>
</QueryClientSharingContext.Provider>
<QueryClientContext.Provider value={props.client}>
{props.children}
</QueryClientContext.Provider>
)
}