Skip to content

Commit

Permalink
More agnostic form component
Browse files Browse the repository at this point in the history
  • Loading branch information
apedroferreira committed Feb 17, 2025
1 parent c42bd20 commit fe6950d
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 167 deletions.
3 changes: 1 addition & 2 deletions docs/pages/toolpad/core/api/edit.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{
"props": {
"dataSource": { "type": { "name": "object" } },
"onSubmitSuccess": { "type": { "name": "func" } },
"resetOnSubmit": { "type": { "name": "bool" } }
"onSubmitSuccess": { "type": { "name": "func" } }
},
"name": "Edit",
"imports": ["import { Edit } from '@toolpad/core/Crud';"],
Expand Down
5 changes: 1 addition & 4 deletions docs/translations/api-docs/edit/edit.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
"componentDescription": "",
"propDescriptions": {
"dataSource": { "description": "Server-side data source." },
"onSubmitSuccess": { "description": "Callback fired when the form is successfully submitted." },
"resetOnSubmit": {
"description": "Whether the form fields should reset after the form is submitted."
}
"onSubmitSuccess": { "description": "Callback fired when the form is successfully submitted." }
},
"classDescriptions": {}
}
119 changes: 105 additions & 14 deletions packages/toolpad-core/src/Crud/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import invariant from 'invariant';
import { CrudForm } from './CrudForm';
import { DataModel, DataSource, OmitId } from './shared';
import { useNotifications } from '../useNotifications';
import { CrudContext } from '../shared/context';
import { DataModel, DataSource, OmitId } from './shared';

export interface CreateProps<D extends DataModel> {
/**
Expand All @@ -19,7 +20,7 @@ export interface CreateProps<D extends DataModel> {
/**
* Callback fired when the form is successfully submitted.
*/
onSubmitSuccess?: () => void;
onSubmitSuccess?: (formValues: Partial<OmitId<D>>) => void | Promise<void>;
/**
* Whether the form fields should reset after the form is submitted.
*/
Expand All @@ -36,36 +37,126 @@ export interface CreateProps<D extends DataModel> {
* - [Create API](https://mui.com/toolpad/core/api/create)
*/
function Create<D extends DataModel>(props: CreateProps<D>) {
const { initialValues, onSubmitSuccess, resetOnSubmit } = props;
const { initialValues = {} as Partial<OmitId<D>>, onSubmitSuccess, resetOnSubmit } = props;

const crudContext = React.useContext(CrudContext);
const dataSource = (props.dataSource ?? crudContext.dataSource) as Exclude<
typeof props.dataSource,
undefined
>;

const notifications = useNotifications();

invariant(dataSource, 'No data source found.');

const { createOne } = dataSource;
const { fields, createOne, validate } = dataSource;

const handleCreate = React.useCallback(
async (formValues: Partial<OmitId<D>>) => {
await createOne(formValues);
const [formState, setFormState] = React.useState<{
values: Partial<OmitId<D>>;
errors: Partial<Record<keyof D, string>>;
}>({
values: {
...Object.fromEntries(
fields
.filter(({ field }) => field !== 'id')
.map(({ field, type }) => [
field,
type === 'boolean' ? (initialValues[field] ?? false) : initialValues[field],
]),
),
...initialValues,
},
errors: {},
});
const formValues = formState.values;
const formErrors = formState.errors;

const setFormValues = React.useCallback((newFormValues: Partial<OmitId<D>>) => {
setFormState((previousState) => ({
...previousState,
values: newFormValues,
}));
}, []);

const setFormErrors = React.useCallback((newFormErrors: Partial<Record<keyof D, string>>) => {
setFormState((previousState) => ({
...previousState,
errors: newFormErrors,
}));
}, []);

const handleFormFieldChange = React.useCallback(
(name: keyof D, value: string | number | boolean | File | null) => {
const validateField = async (values: Partial<OmitId<D>>) => {
if (validate) {
const errors = await validate(values);
setFormErrors({ ...formErrors, [name]: errors[name] });
}
};

const newFormValues = { ...formValues, [name]: value };

setFormValues(newFormValues);
validateField(newFormValues);
},
[createOne],
[formErrors, formValues, setFormErrors, setFormValues, validate],
);

const handleFormReset = React.useCallback(() => {
setFormValues(initialValues);
}, [initialValues, setFormValues]);

const handleFormSubmit = React.useCallback(async () => {
if (validate) {
const errors = await validate(formValues);
if (Object.keys(errors).length > 0) {
setFormErrors(errors);
throw new Error('Form validation failed');
}
}
setFormErrors({});

try {
await createOne(formValues);
notifications.show('Item created successfully.', {
severity: 'success',
autoHideDuration: 3000,
});

if (onSubmitSuccess) {
await onSubmitSuccess(formValues);
}

if (resetOnSubmit) {
handleFormReset();
}
} catch (createError) {
notifications.show(`Failed to create item.\n${(createError as Error).message}`, {
severity: 'error',
autoHideDuration: 3000,
});
throw createError;
}
}, [
createOne,
formValues,
handleFormReset,
notifications,
onSubmitSuccess,
resetOnSubmit,
setFormErrors,
validate,
]);

return (
<CrudForm
dataSource={dataSource}
initialValues={initialValues}
onSubmit={handleCreate}
onSubmitSuccess={onSubmitSuccess}
resetOnSubmit={resetOnSubmit}
formState={formState}
onFieldChange={handleFormFieldChange}
onSubmit={handleFormSubmit}
onReset={handleFormReset}
localeText={{
submitButtonLabel: 'Create',
submitSuccessMessage: 'Item created successfully.',
submitErrorMessage: 'Failed to create item.',
}}
/>
);
Expand Down
Loading

0 comments on commit fe6950d

Please sign in to comment.