@refinedev/core
Version:
refine is a React-based framework for building internal tools, rapidly. It ships with Ant Design System, an enterprise-level UI toolkit.
364 lines (331 loc) • 10.6 kB
text/typescript
import {
pickDataProvider,
pickNotDeprecated,
useActiveAuthProvider,
} from "@definitions/helpers";
import { getXRay } from "@refinedev/devtools-internal";
import {
type UseMutationOptions,
type MutateOptions,
useMutation,
} from "@tanstack/react-query";
import {
useDataProvider,
useHandleNotification,
useInvalidate,
useKeys,
useLog,
useMeta,
useOnError,
usePublish,
useRefineContext,
useResource,
useTranslate,
} from "@hooks";
import type {
BaseRecord,
CreateResponse,
HttpError,
IQueryKeys,
MetaQuery,
} from "../../contexts/data/types";
import type { UseMutationResult } from "../../definitions/types";
import type { SuccessErrorNotification } from "../../contexts/notification/types";
import {
type UseLoadingOvertimeOptionsProps,
type UseLoadingOvertimeReturnType,
useLoadingOvertime,
} from "../useLoadingOvertime";
export type UseCreateParams<TData, TError, TVariables> = {
/**
* Resource name for API data interactions
*/
resource?: string;
/**
* Values for mutation function
*/
values?: TVariables;
/**
* Meta data for `dataProvider`
*/
meta?: MetaQuery;
/**
* Meta data for `dataProvider`
* @deprecated `metaData` is deprecated with refine@4, refine will pass `meta` instead, however, we still support `metaData` for backward compatibility.
*/
metaData?: MetaQuery;
/**
* If there is more than one `dataProvider`, you should use the `dataProviderName` that you will use.
*/
dataProviderName?: string;
/**
* You can use it to manage the invalidations that will occur at the end of the mutation.
*/
invalidates?: Array<keyof IQueryKeys>;
} & SuccessErrorNotification<CreateResponse<TData>, TError, TVariables>;
export type UseCreateReturnType<
TData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables = {},
> = UseMutationResult<
CreateResponse<TData>,
TError,
UseCreateParams<TData, TError, TVariables>,
unknown
>;
export type UseCreateProps<
TData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables = {},
> = {
mutationOptions?: Omit<
UseMutationOptions<
CreateResponse<TData>,
TError,
UseCreateParams<TData, TError, TVariables>,
unknown
>,
"mutationFn"
>;
} & UseLoadingOvertimeOptionsProps &
UseCreateParams<TData, TError, TVariables>;
/**
* `useCreate` is a modified version of `react-query`'s {@link https://react-query.tanstack.com/reference/useMutation `useMutation`} for create mutations.
*
* It uses `create` method as mutation function from the `dataProvider` which is passed to `<Refine>`.
*
* @see {@link https://refine.dev/docs/api-reference/core/hooks/data/useCreate} for more details.
*
* @typeParam TData - Result data of the query extends {@link https://refine.dev/docs/api-reference/core/interfaceReferences#baserecord `BaseRecord`}
* @typeParam TError - Custom error object that extends {@link https://refine.dev/docs/api-reference/core/interfaceReferences/#httperror `HttpError`}
* @typeParam TVariables - Values for mutation function
*
*/
export const useCreate = <
TData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables = {},
>({
resource: resourceFromProps,
values: valuesFromProps,
dataProviderName: dataProviderNameFromProps,
successNotification: successNotificationFromProps,
errorNotification: errorNotificationFromProps,
invalidates: invalidatesFromProps,
meta: metaFromProps,
metaData: metaDataFromProps,
mutationOptions,
overtimeOptions,
}: UseCreateProps<TData, TError, TVariables> = {}): UseCreateReturnType<
TData,
TError,
TVariables
> &
UseLoadingOvertimeReturnType => {
const authProvider = useActiveAuthProvider();
const { mutate: checkError } = useOnError({
v3LegacyAuthProviderCompatible: Boolean(authProvider?.isLegacy),
});
const dataProvider = useDataProvider();
const invalidateStore = useInvalidate();
const { resources, select } = useResource();
const translate = useTranslate();
const publish = usePublish();
const { log } = useLog();
const handleNotification = useHandleNotification();
const getMeta = useMeta();
const {
options: { textTransformers },
} = useRefineContext();
const { keys, preferLegacyKeys } = useKeys();
const mutationResult = useMutation<
CreateResponse<TData>,
TError,
UseCreateParams<TData, TError, TVariables>,
unknown
>({
mutationFn: ({
resource: resourceName = resourceFromProps,
values = valuesFromProps,
meta = metaFromProps,
metaData = metaDataFromProps,
dataProviderName = dataProviderNameFromProps,
}: UseCreateParams<TData, TError, TVariables>) => {
if (!values) throw missingValuesError;
if (!resourceName) throw missingResourceError;
const { resource, identifier } = select(resourceName);
const combinedMeta = getMeta({
resource,
meta: pickNotDeprecated(meta, metaData),
});
return dataProvider(
pickDataProvider(identifier, dataProviderName, resources),
).create<TData, TVariables>({
resource: resource.name,
variables: values,
meta: combinedMeta,
metaData: combinedMeta,
});
},
onSuccess: (data, variables, context) => {
const {
resource: resourceName = resourceFromProps,
successNotification:
successNotificationFromProp = successNotificationFromProps,
dataProviderName: dataProviderNameFromProp = dataProviderNameFromProps,
invalidates = invalidatesFromProps ?? ["list", "many"],
values = valuesFromProps,
meta = metaFromProps,
metaData = metaDataFromProps,
} = variables;
if (!values) throw missingValuesError;
if (!resourceName) throw missingResourceError;
const { resource, identifier } = select(resourceName);
const resourceSingular = textTransformers.singular(identifier);
const dataProviderName = pickDataProvider(
identifier,
dataProviderNameFromProp,
resources,
);
const combinedMeta = getMeta({
resource,
meta: pickNotDeprecated(meta, metaData),
});
const notificationConfig =
typeof successNotificationFromProp === "function"
? successNotificationFromProp(data, values, identifier)
: successNotificationFromProp;
handleNotification(notificationConfig, {
key: `create-${identifier}-notification`,
message: translate(
"notifications.createSuccess",
{
resource: translate(
`${identifier}.${identifier}`,
resourceSingular,
),
},
`Successfully created ${resourceSingular}`,
),
description: translate("notifications.success", "Success"),
type: "success",
});
invalidateStore({
resource: identifier,
dataProviderName,
invalidates,
});
publish?.({
channel: `resources/${resource.name}`,
type: "created",
payload: {
ids: data?.data?.id ? [data.data.id] : undefined,
},
date: new Date(),
meta: {
...combinedMeta,
dataProviderName,
},
});
const {
fields: _fields,
operation: _operation,
variables: _variables,
...rest
} = combinedMeta || {};
log?.mutate({
action: "create",
resource: resource.name,
data: values,
meta: {
dataProviderName,
id: data?.data?.id ?? undefined,
...rest,
},
});
mutationOptions?.onSuccess?.(data, variables, context);
},
onError: (err: TError, variables, context) => {
const {
resource: resourceName = resourceFromProps,
errorNotification:
errorNotificationFromProp = errorNotificationFromProps,
values = valuesFromProps,
} = variables;
if (!values) throw missingValuesError;
if (!resourceName) throw missingResourceError;
checkError(err);
const { identifier } = select(resourceName);
const resourceSingular = textTransformers.singular(identifier);
const notificationConfig =
typeof errorNotificationFromProp === "function"
? errorNotificationFromProp(err, values, identifier)
: errorNotificationFromProp;
handleNotification(notificationConfig, {
key: `create-${identifier}-notification`,
description: err.message,
message: translate(
"notifications.createError",
{
resource: translate(
`${identifier}.${identifier}`,
resourceSingular,
),
statusCode: err.statusCode,
},
`There was an error creating ${resourceSingular} (status code: ${err.statusCode})`,
),
type: "error",
});
mutationOptions?.onError?.(err, variables, context);
},
mutationKey: keys().data().mutation("create").get(preferLegacyKeys),
...mutationOptions,
meta: {
...mutationOptions?.meta,
...getXRay("useCreate", preferLegacyKeys),
},
});
const { mutate, mutateAsync, ...mutation } = mutationResult;
const { elapsedTime } = useLoadingOvertime({
isLoading: mutation.isLoading,
interval: overtimeOptions?.interval,
onInterval: overtimeOptions?.onInterval,
});
// this function is used to make the `variables` parameter optional
const handleMutation = (
variables?: UseCreateParams<TData, TError, TVariables>,
options?: MutateOptions<
CreateResponse<TData>,
TError,
UseCreateParams<TData, TError, TVariables>,
unknown
>,
) => {
return mutate(variables || {}, options);
};
// this function is used to make the `variables` parameter optional
const handleMutateAsync = (
variables?: UseCreateParams<TData, TError, TVariables>,
options?: MutateOptions<
CreateResponse<TData>,
TError,
UseCreateParams<TData, TError, TVariables>,
unknown
>,
) => {
return mutateAsync(variables || {}, options);
};
return {
...mutation,
mutate: handleMutation,
mutateAsync: handleMutateAsync,
overtime: { elapsedTime },
};
};
const missingResourceError = new Error(
"[useCreate]: `resource` is not defined or not matched but is required",
);
const missingValuesError = new Error(
"[useCreate]: `values` is not provided but is required",
);