Skip to content

Commit

Permalink
Merge branch 'next' into ref-263/autosave-improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
yildirayunlu authored Sep 4, 2023
2 parents 80ebff8 + daabcd6 commit f7ce18e
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 84 deletions.
5 changes: 5 additions & 0 deletions .changeset/honest-singers-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@refinedev/core": patch
---

`useInvalidate` now returns a promise that resolves when the invalidation is completed.
9 changes: 9 additions & 0 deletions .changeset/young-months-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@refinedev/core": patch
---

Fine-tuning the invalidation process by setting up additional filters and options for the invalidation.

Now after a successful mutation, refine will invalidate all the queries in the scope but trigger a refetch only for the active queries. If there are any ongoing queries, they will be kept as they are.

After receiving a realtime subscription event, refine will invalidate and refetch only the active queries.
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ invalidate({
});
```

:::info
`invalidate` function returns a promise that resolves when the invalidation process is completed.
:::

## Invalidation Parameters

### `resource`
Expand All @@ -108,15 +112,25 @@ The states you want to invalidate. You can use the following values:
- `"detail"`: Invalidates the `"detail"` state of the given `resource` and `id`.
- `"many"`: Invalidates the `"many"` state of the given `resource`.

### `invalidationFilters` and `invalidationOptions`

> Type: [`InvalidateQueryFilters`](https://tanstack.com/query/latest/docs/react/reference/QueryClient#queryclientinvalidatequeries)
The filters and options applied to the invalidation process when picking which queries to invalidate. By default **refine** applies some filters and options to fine-tune the invalidation process.

By default settings, all the targeted queries are invalidated and the active ones are triggered for a refetch. If there are any ongoing queries, they are kept as they are.

## API Reference

### Invalidation Parameters

| Property | Description | Type | Default |
| ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------- | --------- |
| <div className="required-block"><div>invalidates</div> <div className="required">Required</div></div> | The states you want to invalidate. | `all`, `resourceAll`, `list`, `many`, `detail`, `false` | |
| resource | Resource name for State invalidation. | `string` | |
| id | The `id` to use when invalidating the "detail" state. | [`BaseKey`](/api-reference/core/interfaces.md#basekey) | |
| dataProviderName | The name of the data provider whose state you want to invalidate. | `string` | `default` |
| Property | Description | Type | Default |
| ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| <div className="required-block"><div>invalidates</div> <div className="required">Required</div></div> | The states you want to invalidate. | `all`, `resourceAll`, `list`, `many`, `detail`, `false` | |
| resource | Resource name for State invalidation. | `string` | |
| id | The `id` to use when invalidating the "detail" state. | [`BaseKey`](/api-reference/core/interfaces.md#basekey) | |
| dataProviderName | The name of the data provider whose state you want to invalidate. | `string` | `default` |
| invalidationFilters | The filters to use when picking queries to invalidate | [`InvalidateQueryFilters`](https://tanstack.com/query/latest/docs/react/reference/QueryClient#queryclientinvalidatequeries) | `{ type: "all", refetchType: "active" }` |
| invalidationOptions | The options to use in the invalidation process | [`InvalidateOptions`](https://tanstack.com/query/latest/docs/react/reference/QueryClient#queryclientinvalidatequeries) | `{ cancelRefetch: false }` |

[data-provider]: /docs/api-reference/core/providers/data-provider/
10 changes: 10 additions & 0 deletions documentation/docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,16 @@ const App = () => {
};
```

## How do invalidation works in queries?

**refine** invalidates for the specific scope of queries after a successful mutation. The scope of the invalidation can be customized by the `invalidates` prop in the mutation hooks.

By default, **refine** invalidates all the queries that are in the defined scope and only triggers a refetch for the active queries (mounted and enabled). This is done to prevent unnecessary refetches and for more precise invalidation.

In realtime updates, **refine** will invalidate and refetch all the active queries that are in the defined scope.

In both cases, if there are any ongoing queries, **refine** will keep them as they are and will not invalidate or refetch them.

[use-form-core]: /docs/api-reference/core/hooks/useForm/
[use-form-react-hook-form]: /docs/packages/documentation/react-hook-form/useForm/
[use-form-antd]: /docs/api-reference/antd/hooks/form/useForm/
Expand Down
16 changes: 6 additions & 10 deletions packages/core/src/hooks/auth/useInvalidateAuthStore/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,13 @@ export const useInvalidateAuthStore = () => {
const { keys, preferLegacyKeys } = useKeys();

const invalidate = async () => {
await Promise.all([
queryClient.invalidateQueries(
keys().auth().action("check").get(preferLegacyKeys),
await Promise.all(
(["check", "identity", "permissions"] as const).map((action) =>
queryClient.invalidateQueries(
keys().auth().action(action).get(preferLegacyKeys),
),
),
queryClient.invalidateQueries(
keys().auth().action("identity").get(preferLegacyKeys),
),
queryClient.invalidateQueries(
keys().auth().action("permissions").get(preferLegacyKeys),
),
]);
);
};

return invalidate;
Expand Down
50 changes: 41 additions & 9 deletions packages/core/src/hooks/invalidate/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ describe("useInvalidate", () => {
dataProviderName: "rest",
});

expect(dispatch).toBeCalledWith(["rest", "posts", "list"]);
expect(dispatch).toBeCalledWith(
["rest", "posts", "list"],
expect.anything(),
expect.anything(),
);
});

it("with detail invalidation", async () => {
Expand All @@ -102,9 +106,17 @@ describe("useInvalidate", () => {
id: "1",
});

expect(dispatch).toBeCalledWith(["rest", "posts", "list"]);
expect(dispatch).toHaveBeenCalledWith(
expect.arrayContaining(["rest", "posts", "list"]),
expect.anything(),
expect.anything(),
);

expect(dispatch).toBeCalledWith(["rest", "posts", "detail", "1"]);
expect(dispatch).toHaveBeenCalledWith(
expect.arrayContaining(["rest", "posts", "detail", "1"]),
expect.anything(),
expect.anything(),
);
});

it("with 'all' invalidation", async () => {
Expand All @@ -122,18 +134,38 @@ describe("useInvalidate", () => {
wrapper: TestWrapper({}),
});

result.current({
await result.current({
resource: "posts",
invalidates: ["detail", "all", "list", "many", "resourceAll"],
dataProviderName: "rest",
id: "1",
});

expect(dispatch).toBeCalledWith(["rest"]);
expect(dispatch).toBeCalledWith(["rest", "posts"]);
expect(dispatch).toBeCalledWith(["rest", "posts", "list"]);
expect(dispatch).toBeCalledWith(["rest", "posts", "getMany"]);
expect(dispatch).toBeCalledWith(["rest", "posts", "detail", "1"]);
expect(dispatch).toBeCalledWith(
expect.arrayContaining(["rest", "posts", "detail", "1"]),
expect.anything(),
expect.anything(),
);
expect(dispatch).toBeCalledWith(
expect.arrayContaining(["rest"]),
expect.anything(),
expect.anything(),
);
expect(dispatch).toBeCalledWith(
expect.arrayContaining(["rest", "posts"]),
expect.anything(),
expect.anything(),
);
expect(dispatch).toBeCalledWith(
expect.arrayContaining(["rest", "posts", "list"]),
expect.anything(),
expect.anything(),
);
expect(dispatch).toBeCalledWith(
expect.arrayContaining(["rest", "posts", "getMany"]),
expect.anything(),
expect.anything(),
);
});

it("with 'wrong invalidate key' ", async () => {
Expand Down
93 changes: 56 additions & 37 deletions packages/core/src/hooks/invalidate/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { useCallback } from "react";
import { useQueryClient } from "@tanstack/react-query";
import {
InvalidateOptions,
InvalidateQueryFilters,
useQueryClient,
} from "@tanstack/react-query";

import { useResource } from "@hooks/resource";
import { pickDataProvider } from "@definitions";
Expand All @@ -11,19 +15,25 @@ export type UseInvalidateProp = {
id?: BaseKey;
dataProviderName?: string;
invalidates: Array<keyof IQueryKeys> | false;
invalidationFilters?: InvalidateQueryFilters;
invalidationOptions?: InvalidateOptions;
};

export const useInvalidate = (): ((props: UseInvalidateProp) => void) => {
export const useInvalidate = (): ((
props: UseInvalidateProp,
) => Promise<void>) => {
const { resources } = useResource();
const queryClient = useQueryClient();
const { keys, preferLegacyKeys } = useKeys();

const invalidate = useCallback(
({
async ({
resource,
dataProviderName,
invalidates,
id,
invalidationFilters = { type: "all", refetchType: "active" },
invalidationOptions = { cancelRefetch: false },
}: UseInvalidateProp) => {
if (invalidates === false) {
return;
Expand All @@ -34,40 +44,49 @@ export const useInvalidate = (): ((props: UseInvalidateProp) => void) => {
.data(dp)
.resource(resource ?? "");

invalidates.forEach((key) => {
switch (key) {
case "all":
queryClient.invalidateQueries(
keys().data(dp).get(preferLegacyKeys),
);
break;
case "list":
queryClient.invalidateQueries(
queryKey.action("list").get(preferLegacyKeys),
);
break;
case "many":
queryClient.invalidateQueries(
queryKey.action("many").get(preferLegacyKeys),
);
break;
case "resourceAll":
queryClient.invalidateQueries(
queryKey.get(preferLegacyKeys),
);
break;
case "detail":
queryClient.invalidateQueries(
queryKey
.action("one")
.id(id || "")
.get(preferLegacyKeys),
);
break;
default:
break;
}
});
await Promise.all(
invalidates.map((key) => {
switch (key) {
case "all":
return queryClient.invalidateQueries(
keys().data(dp).get(preferLegacyKeys),
invalidationFilters,
invalidationOptions,
);
case "list":
return queryClient.invalidateQueries(
queryKey.action("list").get(preferLegacyKeys),
invalidationFilters,
invalidationOptions,
);
case "many":
return queryClient.invalidateQueries(
queryKey.action("many").get(preferLegacyKeys),
invalidationFilters,
invalidationOptions,
);
case "resourceAll":
return queryClient.invalidateQueries(
queryKey.get(preferLegacyKeys),
invalidationFilters,
invalidationOptions,
);
case "detail":
return queryClient.invalidateQueries(
queryKey
.action("one")
.id(id || "")
.get(preferLegacyKeys),
invalidationFilters,
invalidationOptions,
);
default:
return;
}
}),
);

return;
},
[],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,10 @@ describe("useResourceSubscription Hook", () => {

expect(onSubscribeMock).toBeCalled();
expect(onLiveEventMock).toBeCalledWith(mockCallbackEventPayload);
expect(invalidateQueriesMock).toBeCalledWith([
"default",
"featured-posts",
]);
expect(invalidateQueriesMock).toBeCalledWith(
["default", "featured-posts"],
expect.anything(),
expect.anything(),
);
});
});
40 changes: 22 additions & 18 deletions packages/core/src/hooks/live/useResourceSubscription/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useContext, useEffect } from "react";
import { useQueryClient } from "@tanstack/react-query";
import {
BaseKey,
CrudFilters,
Expand All @@ -14,7 +13,7 @@ import {
import { LiveContext } from "@contexts/live";
import { RefineContext } from "@contexts/refine";
import { useResource } from "@hooks/resource";
import { useKeys } from "@hooks/useKeys";
import { useInvalidate } from "@hooks/invalidate";

export type UseResourceSubscriptionProps = {
channel: string;
Expand Down Expand Up @@ -58,10 +57,7 @@ export const useResourceSubscription = ({
liveMode: liveModeFromProp,
onLiveEvent,
}: UseResourceSubscriptionProps): void => {
const queryClient = useQueryClient();

const { resource, identifier } = useResource(resourceFromProp);
const { keys, preferLegacyKeys } = useKeys();

const liveDataContext = useContext<ILiveContext>(LiveContext);
const {
Expand All @@ -71,9 +67,29 @@ export const useResourceSubscription = ({

const liveMode = liveModeFromProp ?? liveModeFromContext;

const invalidate = useInvalidate();

useEffect(() => {
let subscription: any;

const callback = (event: LiveEvent) => {
if (liveMode === "auto") {
invalidate({
resource: identifier,
dataProviderName: resource?.meta?.dataProviderName,
invalidates: ["resourceAll"],
invalidationFilters: {
type: "active",
refetchType: "active",
},
invalidationOptions: { cancelRefetch: false },
});
}

onLiveEvent?.(event);
onLiveEventContextCallback?.(event);
};

if (liveMode && liveMode !== "off" && enabled) {
subscription = liveDataContext?.subscribe({
channel,
Expand All @@ -82,19 +98,7 @@ export const useResourceSubscription = ({
...params,
},
types,
callback: (event) => {
if (liveMode === "auto") {
queryClient.invalidateQueries(
keys()
.data(resource?.meta?.dataProviderName)
.resource(identifier)
.get(preferLegacyKeys),
);
}

onLiveEvent?.(event);
onLiveEventContextCallback?.(event);
},
callback,
});
}

Expand Down

0 comments on commit f7ce18e

Please sign in to comment.