packages/core/src/hooks/accessControl/useCan/index.ts
import { useContext } from "react";
import { getXRay } from "@refinedev/devtools-internal";
import {
UseQueryOptions,
UseQueryResult,
useQuery,
} from "@tanstack/react-query";
import { AccessControlContext } from "@contexts/accessControl";
import { sanitizeResource } from "@definitions/helpers/sanitize-resource";
import { useKeys } from "@hooks/useKeys";
import {
CanParams,
CanReturnType,
} from "../../../contexts/accessControl/types";
export type UseCanProps = CanParams & {
/**
* react-query's [useQuery](https://tanstack.com/query/v4/docs/reference/useQuery) options
*/
queryOptions?: UseQueryOptions<CanReturnType>;
};
/**
* `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`.
* @see {@link https://refine.dev/docs/api-reference/core/hooks/accessControl/useCan} for more details.
*
* @typeParam CanParams {@link https://refine.dev/docs/core/interfaceReferences#canparams}
* @typeParam CanReturnType {@link https://refine.dev/docs/core/interfaceReferences#canreturntype}
*
*/
export const useCan = ({
action,
resource,
params,
queryOptions: hookQueryOptions,
}: UseCanProps): UseQueryResult<CanReturnType> => {
const { can, options: globalOptions } = useContext(AccessControlContext);
const { keys, preferLegacyKeys } = useKeys();
const { queryOptions: globalQueryOptions } = globalOptions || {};
const mergedQueryOptions = {
...globalQueryOptions,
...hookQueryOptions,
};
/**
* 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.
* 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`
*/
const { resource: _resource, ...paramsRest } = params ?? {};
const sanitizedResource = sanitizeResource(_resource);
const queryResponse = useQuery<CanReturnType>({
queryKey: keys()
.access()
.resource(resource)
.action(action)
.params({
params: { ...paramsRest, resource: sanitizedResource },
enabled: mergedQueryOptions?.enabled,
})
.get(preferLegacyKeys),
// 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.
queryFn: () =>
can?.({
action,
resource,
params: { ...paramsRest, resource: sanitizedResource },
}) ?? Promise.resolve({ can: true }),
enabled: typeof can !== "undefined",
...mergedQueryOptions,
meta: {
...mergedQueryOptions?.meta,
...getXRay("useCan", preferLegacyKeys),
},
retry: false,
});
return typeof can === "undefined"
? ({ data: { can: true } } as typeof queryResponse)
: queryResponse;
};