Skip to content

Commit 17ee27d

Browse files
BUG-accessControlProvider: resolve cacheTime issue for can function refinedev#6749
1 parent d9585d9 commit 17ee27d

File tree

1 file changed

+135
-29
lines changed
  • packages/core/src/hooks/accessControl/useCan

1 file changed

+135
-29
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,100 @@
1-
import { useContext } from "react";
1+
// import { useContext } from "react";
22

3+
// import { getXRay } from "@refinedev/devtools-internal";
4+
// import {
5+
// type UseQueryOptions,
6+
// type UseQueryResult,
7+
// useQuery,
8+
// } from "@tanstack/react-query";
9+
10+
// import { AccessControlContext } from "@contexts/accessControl";
11+
// import { sanitizeResource } from "@definitions/helpers/sanitize-resource";
12+
// import { useKeys } from "@hooks/useKeys";
13+
// import type {
14+
// CanParams,
15+
// CanReturnType,
16+
// } from "../../../contexts/accessControl/types";
17+
18+
// export type UseCanProps = CanParams & {
19+
// /**
20+
// * react-query's [useQuery](https://tanstack.com/query/v4/docs/reference/useQuery) options
21+
// */
22+
// queryOptions?: UseQueryOptions<CanReturnType>;
23+
// };
24+
25+
// /**
26+
// * `useCan` uses the `can` as the query function for `react-query`'s {@link https://react-query.tanstack.com/guides/queries `useQuery`}. It takes the parameters that `can` takes. It can also be configured with `queryOptions` for `useQuery`. Returns the result of `useQuery`.
27+
// * @see {@link https://refine.dev/docs/api-reference/core/hooks/accessControl/useCan} for more details.
28+
// *
29+
// * @typeParam CanParams {@link https://refine.dev/docs/core/interfaceReferences#canparams}
30+
// * @typeParam CanReturnType {@link https://refine.dev/docs/core/interfaceReferences#canreturntype}
31+
// *
32+
// */
33+
// export const useCan = ({
34+
// action,
35+
// resource,
36+
// params,
37+
// queryOptions: hookQueryOptions,
38+
// }: UseCanProps): UseQueryResult<CanReturnType> => {
39+
// const { can, options: globalOptions } = useContext(AccessControlContext);
40+
// const { keys, preferLegacyKeys } = useKeys();
41+
42+
// const { queryOptions: globalQueryOptions } = globalOptions || {};
43+
44+
// const mergedQueryOptions = {
45+
// ...globalQueryOptions,
46+
// ...hookQueryOptions,
47+
// };
48+
49+
// /**
50+
// * Since `react-query` stringifies the query keys, it will throw an error for a circular dependency if we include `React.ReactNode` elements inside the keys.
51+
// * The feature in #2220(https://github.com/refinedev/refine/issues/2220) includes such change and to fix this, we need to remove `icon` property in the `resource`
52+
// */
53+
// const { resource: _resource, ...paramsRest } = params ?? {};
54+
55+
// const sanitizedResource = sanitizeResource(_resource);
56+
57+
// const queryResponse = useQuery<CanReturnType>({
58+
// queryKey: keys()
59+
// .access()
60+
// .resource(resource)
61+
// .action(action)
62+
// .params({
63+
// params: { ...paramsRest, resource: sanitizedResource },
64+
// enabled: mergedQueryOptions?.enabled,
65+
// })
66+
// .get(preferLegacyKeys),
67+
// // Enabled check for `can` is enough to be sure that it's defined in the query function but TS is not smart enough to know that.
68+
// queryFn: () =>
69+
// can?.({
70+
// action,
71+
// resource,
72+
// params: { ...paramsRest, resource: sanitizedResource },
73+
// }) ?? Promise.resolve({ can: true }),
74+
// enabled: typeof can !== "undefined",
75+
// ...mergedQueryOptions,
76+
// meta: {
77+
// ...mergedQueryOptions?.meta,
78+
// ...getXRay("useCan", preferLegacyKeys, resource, [
79+
// "useButtonCanAccess",
80+
// "useNavigationButton",
81+
// ]),
82+
// },
83+
// retry: false,
84+
// });
85+
86+
// return typeof can === "undefined"
87+
// ? ({ data: { can: true } } as typeof queryResponse)
88+
// : queryResponse;
89+
// };
90+
91+
import { useContext, useRef } from "react";
392
import { getXRay } from "@refinedev/devtools-internal";
493
import {
594
type UseQueryOptions,
695
type UseQueryResult,
796
useQuery,
897
} from "@tanstack/react-query";
9-
1098
import { AccessControlContext } from "@contexts/accessControl";
1199
import { sanitizeResource } from "@definitions/helpers/sanitize-resource";
12100
import { useKeys } from "@hooks/useKeys";
@@ -23,8 +111,13 @@ export type UseCanProps = CanParams & {
23111
};
24112

25113
/**
26-
* `useCan` uses the `can` as the query function for `react-query`'s {@link https://react-query.tanstack.com/guides/queries `useQuery`}. It takes the parameters that `can` takes. It can also be configured with `queryOptions` for `useQuery`. Returns the result of `useQuery`.
27-
* @see {@link https://refine.dev/docs/api-reference/core/hooks/accessControl/useCan} for more details.
114+
* Custom cache for `can` function results to optimize performance.
115+
*/
116+
const canCache = new Map<string, { result: CanReturnType; timestamp: number }>();
117+
118+
/**
119+
* `useCan` uses the `can` as the query function for `react-query`'s {@link https://react-query.tanstack.com/guides/queries `useQuery`}.
120+
* It now includes a custom cache for `can` results to optimize performance by reducing repeated calls.
28121
*
29122
* @typeParam CanParams {@link https://refine.dev/docs/core/interfaceReferences#canparams}
30123
* @typeParam CanReturnType {@link https://refine.dev/docs/core/interfaceReferences#canreturntype}
@@ -39,22 +132,39 @@ export const useCan = ({
39132
const { can, options: globalOptions } = useContext(AccessControlContext);
40133
const { keys, preferLegacyKeys } = useKeys();
41134

42-
const { queryOptions: globalQueryOptions } = globalOptions || {};
43-
44-
const mergedQueryOptions = {
45-
...globalQueryOptions,
46-
...hookQueryOptions,
135+
const mergedQueryOptions: UseQueryOptions<CanReturnType> = {
136+
...(globalOptions?.queryOptions ?? {}),
137+
...(hookQueryOptions ?? {}),
138+
enabled:
139+
hookQueryOptions?.enabled ??
140+
globalOptions?.queryOptions?.enabled ??
141+
typeof can !== "undefined",
142+
meta: {
143+
...(globalOptions?.queryOptions?.meta ?? {}),
144+
...(hookQueryOptions?.meta ?? {}),
145+
...getXRay("useCan", preferLegacyKeys, resource, [
146+
"useButtonCanAccess",
147+
"useNavigationButton",
148+
]),
149+
},
150+
retry: false,
47151
};
48152

49-
/**
50-
* Since `react-query` stringifies the query keys, it will throw an error for a circular dependency if we include `React.ReactNode` elements inside the keys.
51-
* The feature in #2220(https://github.com/refinedev/refine/issues/2220) includes such change and to fix this, we need to remove `icon` property in the `resource`
52-
*/
53153
const { resource: _resource, ...paramsRest } = params ?? {};
54-
55154
const sanitizedResource = sanitizeResource(_resource);
56155

156+
// Custom caching logic
157+
const cacheKey = `${resource}-${action}-${JSON.stringify(paramsRest)}`;
158+
const cacheDuration = mergedQueryOptions?.staleTime || 5 * 60 * 1000; // Default to 5 minutes
159+
const cacheEntry = canCache.get(cacheKey);
160+
161+
// Check if we have a cached value and if it's still valid
162+
if (cacheEntry && Date.now() - cacheEntry.timestamp < cacheDuration) {
163+
return { data: cacheEntry.result } as UseQueryResult<CanReturnType>;
164+
}
165+
57166
const queryResponse = useQuery<CanReturnType>({
167+
...mergedQueryOptions,
58168
queryKey: keys()
59169
.access()
60170
.resource(resource)
@@ -64,26 +174,22 @@ export const useCan = ({
64174
enabled: mergedQueryOptions?.enabled,
65175
})
66176
.get(preferLegacyKeys),
67-
// Enabled check for `can` is enough to be sure that it's defined in the query function but TS is not smart enough to know that.
68-
queryFn: () =>
69-
can?.({
177+
queryFn: async () => {
178+
const result = await (can?.({
70179
action,
71180
resource,
72181
params: { ...paramsRest, resource: sanitizedResource },
73-
}) ?? Promise.resolve({ can: true }),
74-
enabled: typeof can !== "undefined",
75-
...mergedQueryOptions,
76-
meta: {
77-
...mergedQueryOptions?.meta,
78-
...getXRay("useCan", preferLegacyKeys, resource, [
79-
"useButtonCanAccess",
80-
"useNavigationButton",
81-
]),
182+
}) ?? Promise.resolve({ can: true }));
183+
184+
// Cache the result for future use
185+
canCache.set(cacheKey, { result, timestamp: Date.now() });
186+
187+
return result;
82188
},
189+
enabled: typeof can !== "undefined",
190+
meta: mergedQueryOptions.meta,
83191
retry: false,
84192
});
85193

86-
return typeof can === "undefined"
87-
? ({ data: { can: true } } as typeof queryResponse)
88-
: queryResponse;
194+
return queryResponse;
89195
};

0 commit comments

Comments
 (0)