ra-core
Version:
Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React
256 lines (245 loc) • 8.42 kB
text/typescript
import { useCallback } from 'react';
import { UseMutationOptions } from '@tanstack/react-query';
import { useAuthenticated, useRequireAccess } from '../../auth';
import {
HttpError,
useCreate,
UseCreateMutateParams,
} from '../../dataProvider';
import { useRedirect, RedirectionSideEffect } from '../../routing';
import { useNotify } from '../../notification';
import {
SaveContextValue,
SaveHandlerCallbacks,
useMutationMiddlewares,
} from '../saveContext';
import { useTranslate } from '../../i18n';
import { Identifier, MutationMode, RaRecord, TransformData } from '../../types';
import {
useResourceContext,
useResourceDefinition,
useGetResourceLabel,
} from '../../core';
/**
* Prepare data for the Create view
*
* @param {Object} props The props passed to the Create component.
*
* @return {Object} controllerProps Fetched data and callbacks for the Create view
*
* @example
*
* import { useCreateController } from 'react-admin';
* import CreateView from './CreateView';
*
* const MyCreate = props => {
* const controllerProps = useCreateController(props);
* return <CreateView {...controllerProps} {...props} />;
* }
*/
export const useCreateController = <
RecordType extends Omit<RaRecord, 'id'> = any,
MutationOptionsError = Error,
ResultRecordType extends RaRecord = RecordType & { id: Identifier },
>(
props: CreateControllerProps<
RecordType,
MutationOptionsError,
ResultRecordType
> = {}
): CreateControllerResult<RecordType> => {
const {
disableAuthentication,
record,
redirect: redirectTo,
transform,
mutationMode = 'pessimistic',
mutationOptions = {},
} = props;
const resource = useResourceContext(props);
if (!resource) {
throw new Error(
'useCreateController requires a non-empty resource prop or context'
);
}
const { isPending: isPendingAuthenticated } = useAuthenticated({
enabled: !disableAuthentication,
});
const { isPending: isPendingCanAccess } = useRequireAccess<RecordType>({
action: 'create',
resource,
enabled: !disableAuthentication && !isPendingAuthenticated,
});
const { hasEdit, hasShow } = useResourceDefinition(props);
const finalRedirectTo =
redirectTo ?? getDefaultRedirectRoute(hasShow, hasEdit);
const translate = useTranslate();
const notify = useNotify();
const redirect = useRedirect();
const { onSuccess, onError, meta, ...otherMutationOptions } =
mutationOptions;
const {
registerMutationMiddleware,
getMutateWithMiddlewares,
unregisterMutationMiddleware,
} = useMutationMiddlewares();
const [create, { isPending: saving }] = useCreate<
RecordType,
MutationOptionsError,
ResultRecordType
>(resource, undefined, {
onSuccess: async (data, variables, context) => {
if (onSuccess) {
return onSuccess(data, variables, context);
}
notify(`resources.${resource}.notifications.created`, {
type: 'info',
messageArgs: {
smart_count: 1,
_: translate(`ra.notification.created`, {
smart_count: 1,
}),
},
undoable: mutationMode === 'undoable',
});
redirect(finalRedirectTo, resource, data.id, data);
},
onError: (error: MutationOptionsError, variables, context) => {
if (onError) {
return onError(error, variables, context);
}
// Don't trigger a notification if this is a validation error
// (notification will be handled by the useNotifyIsFormInvalid hook)
const validationErrors = (error as HttpError)?.body?.errors;
const hasValidationErrors =
!!validationErrors && Object.keys(validationErrors).length > 0;
if (!hasValidationErrors || mutationMode !== 'pessimistic') {
notify(
typeof error === 'string'
? error
: (error as Error).message ||
'ra.notification.http_error',
{
type: 'error',
messageArgs: {
_:
typeof error === 'string'
? error
: error instanceof Error ||
(typeof error === 'object' &&
error !== null &&
error.hasOwnProperty('message'))
? // @ts-ignore
error.message
: undefined,
},
}
);
}
},
...otherMutationOptions,
mutationMode,
returnPromise: mutationMode === 'pessimistic',
getMutateWithMiddlewares,
});
const save = useCallback(
(
data: Partial<RecordType>,
{
onSuccess: onSuccessFromSave,
onError: onErrorFromSave,
transform: transformFromSave,
meta: metaFromSave,
} = {} as SaveHandlerCallbacks
) =>
Promise.resolve(
transformFromSave
? transformFromSave(data)
: transform
? transform(data)
: data
).then(async (data: Partial<RecordType>) => {
try {
await create(
resource,
{ data, meta: metaFromSave ?? meta },
{
onError: onErrorFromSave,
onSuccess: onSuccessFromSave,
}
);
} catch (error) {
if (
(error instanceof HttpError ||
(typeof error === 'object' &&
error !== null &&
error.hasOwnProperty('body'))) &&
error.body?.errors != null
) {
return error.body.errors;
}
}
}),
[create, meta, resource, transform]
);
const getResourceLabel = useGetResourceLabel();
const defaultTitle = translate(`resources.${resource}.page.create`, {
_: translate('ra.page.create', {
name: getResourceLabel(resource, 1),
}),
});
return {
isFetching: false,
isLoading: false,
isPending: disableAuthentication ? false : isPendingCanAccess,
mutationMode,
saving,
defaultTitle,
save,
record,
resource,
redirect: finalRedirectTo,
registerMutationMiddleware,
unregisterMutationMiddleware,
};
};
export interface CreateControllerProps<
RecordType extends Omit<RaRecord, 'id'> = any,
MutationOptionsError = Error,
ResultRecordType extends RaRecord = RecordType & { id: Identifier },
> {
disableAuthentication?: boolean;
hasEdit?: boolean;
hasShow?: boolean;
record?: Partial<RecordType>;
redirect?: RedirectionSideEffect;
resource?: string;
mutationMode?: MutationMode;
mutationOptions?: UseMutationOptions<
ResultRecordType,
MutationOptionsError,
UseCreateMutateParams<RecordType>
> & { meta?: any };
transform?: TransformData;
}
export interface CreateControllerResult<
RecordType extends Omit<RaRecord, 'id'> = any,
> extends SaveContextValue {
defaultTitle?: string;
isFetching: boolean;
isPending: boolean;
isLoading: boolean;
record?: Partial<RecordType>;
redirect: RedirectionSideEffect;
resource: string;
saving: boolean;
}
const getDefaultRedirectRoute = (hasShow, hasEdit) => {
if (hasEdit) {
return 'edit';
}
if (hasShow) {
return 'show';
}
return 'list';
};