Skip to content

Commit

Permalink
feat: Support the ability to provide a context (#2548)
Browse files Browse the repository at this point in the history
* Support providing a context

* Addressing comments and merge conflicts from rebase
  • Loading branch information
blackarctic authored Mar 23, 2022
1 parent cba4503 commit f31e1ed
Show file tree
Hide file tree
Showing 31 changed files with 833 additions and 91 deletions.
8 changes: 0 additions & 8 deletions .vscode/settings.json

This file was deleted.

3 changes: 3 additions & 0 deletions docs/src/pages/devtools.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ReactQueryDevtools } from 'react-query/devtools'
```

By default, React Query Devtools are only included in bundles when `process.env.NODE_ENV === 'development'`, so you don't need to worry about excluding them during a production build.

## Floating Mode

Floating Mode will mount the devtools as a fixed, floating element in your app and provide a toggle in the corner of the screen to show and hide the devtools. This toggle state will be stored and remembered in localStorage across reloads.
Expand Down Expand Up @@ -50,6 +51,8 @@ function App() {
- `position?: "top-left" | "top-right" | "bottom-left" | "bottom-right"`
- Defaults to `bottom-left`
- The position of the React Query logo to open and close the devtools panel
- `context?: React.Context<QueryClient | undefined>`
- Use this to use a custom React Query context. Otherwise, `defaultContext` will be used.

## Embedded Mode

Expand Down
87 changes: 87 additions & 0 deletions docs/src/pages/guides/migrating-to-react-query-4.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,90 @@ When using the [functional updater form of setQueryData](../reference/QueryClien
(previousTodo) => previousTodo ? { ...previousTodo, done: true } : undefined
)
```

### Custom Contexts for Multiple Providers

Custom contexts can now be specified to pair hooks with their matching `Provider`. This is critical when there may be multiple React Query `Provider` instances in the component tree and you need to ensure your hook uses the correct `Provider` instance.

An example:

1) Create a data package.

```tsx
// Our first data package: @my-scope/container-data
const context = React.createContext<QueryClient | undefined>();
const queryCache = new QueryCache()
const queryClient = new QueryClient({ queryCache, context })
export const useUser = () => {
return useQuery(USER_KEY, USER_FETCHER, {
context,
})
}
export const ContainerDataProvider: React.FC = ({ children }) => {
return (
<QueryClientProvider client={queryClient} context={context}>
{children}
</QueryClientProvider>
);
}
```

2) Create a second data package.

```tsx
// Our second data package: @my-scope/my-component-data
const context = React.createContext<QueryClient | undefined>();
const queryCache = new QueryCache()
const queryClient = new QueryClient({ queryCache, context })
export const useItems = () => {
return useQuery(ITEMS_KEY, ITEMS_FETCHER, {
context,
})
}
export const MyComponentDataProvider: React.FC = ({ children }) => {
return (
<QueryClientProvider client={queryClient} context={context}>
{children}
</QueryClientProvider>
);
}
```

3) Use these two data packages in your application.

```tsx
// Our application
import { ContainerDataProvider, useUser } from "@my-scope/container-data";
import { AppDataProvider } from "@my-scope/app-data";
import { MyComponentDataProvider, useItems } from "@my-scope/my-component-data";
<ContainerDataProvider> // <-- Provides container data (like "user") using its own React Query provider
...
<AppDataProvider> // <-- Provides app data using its own React Query provider (unused in this example)
...
<MyComponentDataProvider> // <-- Provides component data (like "items") using its own React Query provider
<MyComponent />
</MyComponentDataProvider>
...
</AppDataProvider>
...
</ContainerDataProvider>
// Example of hooks provided by the "DataProvider" components above:
const MyComponent = () => {
const user = useUser(); // <-- Uses the context specified in ContainerDataProvider.
const items = useItems(); // <-- Uses the context specified in MyComponentDataProvider
...
}
```
5 changes: 4 additions & 1 deletion docs/src/pages/reference/QueryClientProvider.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ function App() {
return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
}
```

**Options**

- `client: QueryClient`
- **Required**
- the QueryClient instance to provide
- `contextSharing: boolean`
- 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.
- 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.
6 changes: 6 additions & 0 deletions docs/src/pages/reference/hydration.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ hydrate(queryClient, dehydratedState, options)
- Optional
- `mutations: MutationOptions` The default mutation options to use for the hydrated mutations.
- `queries: QueryOptions` The default query options to use for the hydrated queries.
- `context?: React.Context<QueryClient | undefined>`
- Use this to use a custom React Query context. Otherwise, `defaultContext` will be used.

### Limitations

Expand All @@ -108,6 +110,8 @@ useHydrate(dehydratedState, options)
- Optional
- `defaultOptions: QueryOptions`
- The default query options to use for the hydrated queries.
- `context?: React.Context<QueryClient | undefined>`
- Use this to use a custom React Query context. Otherwise, `defaultContext` will be used.

## `Hydrate`

Expand All @@ -129,3 +133,5 @@ function App() {
- Optional
- `defaultOptions: QueryOptions`
- The default query options to use for the hydrated queries.
- `context?: React.Context<QueryClient | undefined>`
- Use this to use a custom React Query context. Otherwise, `defaultContext` will be used.
2 changes: 2 additions & 0 deletions docs/src/pages/reference/useIsFetching.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const isFetchingPosts = useIsFetching(['posts'])

- `queryKey?: QueryKey`: [Query Keys](../guides/query-keys)
- `filters?: QueryFilters`: [Query Filters](../guides/filters#query-filters)
- `context?: React.Context<QueryClient | undefined>`
- Use this to use a custom React Query context. Otherwise, `defaultContext` will be used.

**Returns**

Expand Down
2 changes: 2 additions & 0 deletions docs/src/pages/reference/useIsMutating.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const isMutatingPosts = useIsMutating(['posts'])

- `mutationKey?: string | unknown[]`
- `filters?: MutationFilters`: [Mutation Filters](../guides/filters#mutation-filters)
- `context?: React.Context<QueryClient | undefined>`
- Use this to use a custom React Query context. Otherwise, `defaultContext` will be used.

**Returns**

Expand Down
2 changes: 2 additions & 0 deletions docs/src/pages/reference/useMutation.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ mutate(variables, {
- `meta: Record<string, unknown>`
- Optional
- If set, stores additional information on the mutation cache entry that can be used as needed. It will be accessible wherever the `mutation` is available (eg. `onError`, `onSuccess` functions of the `MutationCache`).
- `context?: React.Context<QueryClient | undefined>`
- Use this to use a custom React Query context. Otherwise, `defaultContext` will be used.

**Returns**

Expand Down
5 changes: 4 additions & 1 deletion docs/src/pages/reference/useQueries.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ const results = useQueries({

**Options**

The `useQueries` hook accepts an options object with a **queries** key whose value is an array with query option objects identical to the [`useQuery` hook](/reference/useQuery).
The `useQueries` hook accepts an options object with a **queries** key whose value is an array with query option objects identical to the [`useQuery` hook](/reference/useQuery) (excluding the `context` option).

- `context?: React.Context<QueryClient | undefined>`
- Use this to use a custom React Query context. Otherwise, `defaultContext` will be used.

**Returns**

Expand Down
2 changes: 2 additions & 0 deletions docs/src/pages/reference/useQuery.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ const result = useQuery({
- `meta: Record<string, unknown>`
- Optional
- If set, stores additional information on the query cache entry that can be used as needed. It will be accessible wherever the `query` is available, and is also part of the `QueryFunctionContext` provided to the `queryFn`.
- `context?: React.Context<QueryClient | undefined>`
- Use this to use a custom React Query context. Otherwise, `defaultContext` will be used.

**Returns**

Expand Down
3 changes: 2 additions & 1 deletion src/core/hydration.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ContextOptions } from '../reactjs/types'
import type { QueryClient } from './queryClient'
import type { Query, QueryState } from './query'
import type {
Expand All @@ -17,7 +18,7 @@ export interface DehydrateOptions {
shouldDehydrateQuery?: ShouldDehydrateQueryFunction
}

export interface HydrateOptions {
export interface HydrateOptions extends ContextOptions {
defaultOptions?: {
queries?: QueryOptions
mutations?: MutationOptions
Expand Down
16 changes: 11 additions & 5 deletions src/core/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,17 @@ export function parseFilterArgs<
: [arg1 || {}, arg2]) as [TFilters, TOptions]
}

export function parseMutationFilterArgs(
arg1?: QueryKey | MutationFilters,
arg2?: MutationFilters
): MutationFilters | undefined {
return isQueryKey(arg1) ? { ...arg2, mutationKey: arg1 } : arg1
export function parseMutationFilterArgs<
TFilters extends MutationFilters,
TOptions = unknown
>(
arg1?: QueryKey | TFilters,
arg2?: TFilters | TOptions,
arg3?: TOptions
): [TFilters, TOptions | undefined] {
return (isQueryKey(arg1)
? [{ ...arg2, mutationKey: arg1 }, arg3]
: [arg1 || {}, arg2]) as [TFilters, TOptions]
}

export function matchQuery(
Expand Down
25 changes: 19 additions & 6 deletions src/devtools/devtools.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import React from 'react'

import { Query, useQueryClient, onlineManager } from 'react-query'
import {
Query,
ContextOptions,
useQueryClient,
onlineManager,
} from 'react-query'
import { matchSorter } from 'match-sorter'
import useLocalStorage from './useLocalStorage'
import { useIsMounted, useSafeState } from './utils'
Expand All @@ -21,7 +26,7 @@ import Explorer from './Explorer'
import Logo from './Logo'
import { noop } from '../core/utils'

interface DevtoolsOptions {
interface DevtoolsOptions extends ContextOptions {
/**
* Set this true if you want the dev tools to default to being open
*/
Expand Down Expand Up @@ -60,7 +65,7 @@ interface DevtoolsOptions {
containerElement?: string | any
}

interface DevtoolsPanelOptions {
interface DevtoolsPanelOptions extends ContextOptions {
/**
* The standard React style object used to style a component with inline styles
*/
Expand Down Expand Up @@ -92,6 +97,7 @@ export function ReactQueryDevtools({
toggleButtonProps = {},
position = 'bottom-left',
containerElement: Container = 'aside',
context,
}: DevtoolsOptions): React.ReactElement | null {
const rootRef = React.useRef<HTMLDivElement>(null)
const panelRef = React.useRef<HTMLDivElement>(null)
Expand Down Expand Up @@ -229,6 +235,7 @@ export function ReactQueryDevtools({
<ThemeProvider theme={theme}>
<ReactQueryDevtoolsPanel
ref={panelRef as any}
context={context}
{...otherPanelProps}
style={{
position: 'fixed',
Expand Down Expand Up @@ -381,9 +388,15 @@ export const ReactQueryDevtoolsPanel = React.forwardRef<
HTMLDivElement,
DevtoolsPanelOptions
>(function ReactQueryDevtoolsPanel(props, ref): React.ReactElement {
const { isOpen = true, setIsOpen, handleDragStart, ...panelProps } = props

const queryClient = useQueryClient()
const {
isOpen = true,
setIsOpen,
handleDragStart,
context,
...panelProps
} = props

const queryClient = useQueryClient({ context })
const queryCache = queryClient.getQueryCache()

const [sort, setSort] = useLocalStorage(
Expand Down
Loading

0 comments on commit f31e1ed

Please sign in to comment.