Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export async function GET(req: NextRequest) {
}

export async function POST(req: NextRequest) {
const body: Partial<OmitId<Employee>> = await req.json();
const body: OmitId<Employee> = await req.json();

const employeesStore = getEmployeesStore();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export async function getEmployees(req: NextApiRequest, res: NextApiResponse) {
}

export async function createEmployee(req: NextApiRequest, res: NextApiResponse) {
const body: Partial<OmitId<Employee>> = req.body;
const body: OmitId<Employee> = req.body;

const employeesStore = getEmployeesStore();

Expand Down
2 changes: 1 addition & 1 deletion examples/core/crud-nextjs/src/app/api/employees/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export async function GET(req: NextRequest) {
}

export async function POST(req: NextRequest) {
const body: Partial<OmitId<Employee>> = await req.json();
const body: OmitId<Employee> = await req.json();

const employeesStore = getEmployeesStore();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export async function GET(req: NextRequest) {
}

export async function POST(req: NextRequest) {
const body: Partial<OmitId<Employee>> = await req.json();
const body: OmitId<Employee> = await req.json();

const employeesStore = getEmployeesStore();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export async function getEmployees(req: NextApiRequest, res: NextApiResponse) {
}

export async function createEmployee(req: NextApiRequest, res: NextApiResponse) {
const body: Partial<OmitId<Employee>> = req.body;
const body: OmitId<Employee> = req.body;

const employeesStore = getEmployeesStore();

Expand Down
34 changes: 29 additions & 5 deletions packages/toolpad-core/src/Crud/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface CreateProps<D extends DataModel> {
/**
* Callback fired when the form is successfully submitted.
*/
onSubmitSuccess?: (formValues: Partial<OmitId<D>>) => void | Promise<void>;
onSubmitSuccess?: (formValues: OmitId<D>) => void | Promise<void>;
/**
* Whether the form fields should reset after the form is submitted.
* @default false
Expand Down Expand Up @@ -118,7 +118,7 @@ function Create<D extends DataModel>(props: CreateProps<D>) {
.filter(({ field, editable }) => field !== 'id' && editable !== false)
.map(({ field, type }) => [
field,
type === 'boolean' ? (initialValues[field] ?? false) : initialValues[field],
type === 'boolean' ? (initialValues?.[field] ?? false) : initialValues?.[field],
]),
),
...initialValues,
Expand Down Expand Up @@ -167,8 +167,31 @@ function Create<D extends DataModel>(props: CreateProps<D>) {
}, [initialValues, setFormValues]);

const handleFormSubmit = React.useCallback(async () => {
// Check if all required fields are present
const requiredFields = fields.filter(
({ field, editable }) => field !== 'id' && editable !== false,
);
const missingFields = requiredFields.filter(
({ field }) =>
formValues[field] === undefined || formValues[field] === null || formValues[field] === '',
);

if (missingFields.length > 0) {
const missingFieldErrors = Object.fromEntries(
missingFields.map(({ field, headerName }) => [
field as keyof D,
`${headerName || field} is required`,
]),
) as Partial<Record<keyof D, string>>;
setFormErrors(missingFieldErrors);
throw new Error('Required fields are missing');
}

// At this point, we know all required fields are present, so we can safely cast to OmitId<D>
const completeFormValues = formValues as unknown as OmitId<D>;

if (validate) {
const { issues } = await validate(formValues);
const { issues } = await validate(completeFormValues);
if (issues && issues.length > 0) {
setFormErrors(Object.fromEntries(issues.map((issue) => [issue.path?.[0], issue.message])));
throw new Error('Form validation failed');
Expand All @@ -177,14 +200,14 @@ function Create<D extends DataModel>(props: CreateProps<D>) {
setFormErrors({});

try {
await createOne(formValues);
await createOne(completeFormValues);
notifications.show(localeText.createSuccessMessage, {
severity: 'success',
autoHideDuration: 3000,
});

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

if (resetOnSubmit) {
Expand All @@ -199,6 +222,7 @@ function Create<D extends DataModel>(props: CreateProps<D>) {
}
}, [
createOne,
fields,
formValues,
handleFormReset,
localeText.createErrorMessage,
Expand Down
7 changes: 4 additions & 3 deletions packages/toolpad-core/src/Crud/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,14 @@ export interface DataSource<D extends DataModel> {
filterModel: GridFilterModel;
}) => { items: D[]; itemCount: number } | Promise<{ items: D[]; itemCount: number }>;
getOne?: (id: DataModelId) => D | Promise<D>;
createOne?: (data: Partial<OmitId<D>>) => D | Promise<D>;
createOne?: (data: OmitId<D>) => D | Promise<D>;
updateOne?: (id: DataModelId, data: Partial<OmitId<D>>) => D | Promise<D>;
deleteOne?: (id: DataModelId) => void | Promise<void>;
/**
* Function to validate form values. Follows the Standard Schema `validate` function format (https://standardschema.dev/).
* Can validate either complete records (for create) or partial records (for update).
*/
validate?: (
value: Partial<OmitId<D>>,
) => ReturnType<StandardSchemaV1<Partial<OmitId<D>>>['~standard']['validate']>;
value: OmitId<D> | Partial<OmitId<D>>,
) => ReturnType<StandardSchemaV1<OmitId<D> | Partial<OmitId<D>>>['~standard']['validate']>;
}