Skip to content
Merged
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
2 changes: 2 additions & 0 deletions src/create/CreateGuesser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const IntrospectedCreateGuesser = ({
schema,
schemaAnalyzer,
resource,
mutationMode = 'pessimistic',
mutationOptions,
redirect: redirectTo = 'list',
mode,
Expand All @@ -70,6 +71,7 @@ export const IntrospectedCreateGuesser = ({
resource,
schemaAnalyzer,
fields,
mutationMode,
mutationOptions,
transform,
redirectTo,
Expand Down
1 change: 1 addition & 0 deletions src/edit/EditGuesser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export const IntrospectedEditGuesser = ({
schemaAnalyzer,
fields,
mutationOptions,
mutationMode,
transform,
redirectTo,
children: [],
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -525,4 +525,5 @@ export type UseOnSubmitProps = Pick<
'schemaAnalyzer' | 'resource' | 'fields'
> &
Pick<CreateProps, 'mutationOptions' | 'transform'> &
PickRename<CreateProps, 'redirect', 'redirectTo'>;
PickRename<CreateProps, 'redirect', 'redirectTo'> &
Partial<Pick<EditProps, 'mutationMode'>>;
75 changes: 72 additions & 3 deletions src/useOnSubmit.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import * as React from 'react';
import { jest } from '@jest/globals';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { render, waitFor } from '@testing-library/react';
import type { CreateResult, RaRecord, UpdateResult } from 'react-admin';
import { DataProviderContext, testDataProvider } from 'react-admin';
import {
type CreateResult,
DataProviderContext,
type MutationMode,
type RaRecord,
type UpdateResult,
testDataProvider,
} from 'react-admin';
import { MemoryRouter, Route, Routes } from 'react-router-dom';

import useOnSubmit from './useOnSubmit.js';
import schemaAnalyzer from './hydra/schemaAnalyzer.js';
import { API_FIELDS_DATA } from './__fixtures__/parsedData.js';

Expand All @@ -23,6 +28,16 @@ const onSubmitProps = {
};

jest.mock('./getIdentifierValue.js');
const notify = jest.fn();
const reactAdminActual = jest.requireActual('react-admin') as Record<
string,
unknown
>;
jest.mock('react-admin', () => ({
__esModule: true,
...reactAdminActual,
useNotify: () => notify,
}));

test.each([
{
Expand All @@ -41,6 +56,7 @@ test.each([
])(
'Call create with file input ($name)',
async (values: Omit<RaRecord, 'id'>) => {
const { default: useOnSubmit } = await import('./useOnSubmit.js');
let save;
const Dummy = () => {
const onSubmit = useOnSubmit(onSubmitProps);
Expand Down Expand Up @@ -93,6 +109,7 @@ test.each([
cover: null,
},
])('Call update without file inputs ($name)', async (values: RaRecord) => {
const { default: useOnSubmit } = await import('./useOnSubmit.js');
let save;
const Dummy = () => {
const onSubmit = useOnSubmit(onSubmitProps);
Expand Down Expand Up @@ -125,3 +142,55 @@ test.each([
});
});
});

test.each`
submissionErrors | mutationMode | shouldNotify
${{ name: 'Required' }} | ${'pessimistic'} | ${false}
${{ name: 'Required' }} | ${'optimistic'} | ${true}
${{ name: 'Required' }} | ${'undoable'} | ${true}
${null} | ${'pessimistic'} | ${true}
`(
'notification handling on validation errors ($submissionErrors, $mutationMode)',
async ({ submissionErrors, mutationMode, shouldNotify }) => {
const { default: useOnSubmit } = await import('./useOnSubmit.js');
notify.mockClear();
dataProvider.create = jest.fn(() =>
Promise.reject(new Error('Service Unavailable')),
);

let save;
const Dummy = () => {
const onSubmit = useOnSubmit({
...onSubmitProps,
mutationMode: mutationMode as MutationMode,
schemaAnalyzer: {
...onSubmitProps.schemaAnalyzer,
getSubmissionErrors: () => submissionErrors,
},
});
save = onSubmit;
return <span />;
};

render(
<DataProviderContext.Provider value={dataProvider}>
<QueryClientProvider client={new QueryClient()}>
<MemoryRouter initialEntries={['/books/create']}>
<Routes>
<Route path="/books/create" element={<Dummy />} />
</Routes>
</MemoryRouter>
</QueryClientProvider>
</DataProviderContext.Provider>,
);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const errors = await save({ author: 'Author 1' });

await waitFor(() => {
expect(dataProvider.create).toHaveBeenCalled();
});
(shouldNotify ? expect(notify) : expect(notify).not).toHaveBeenCalled();
expect(errors).toEqual(submissionErrors ?? {});
},
);
18 changes: 10 additions & 8 deletions src/useOnSubmit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const useOnSubmit = ({
schemaAnalyzer,
fields,
mutationOptions,
mutationMode = 'pessimistic',
transform,
redirectTo = 'list',
}: UseOnSubmitProps): ((
Expand Down Expand Up @@ -92,11 +93,14 @@ const useOnSubmit = ({
const failure =
mutationOptions?.onError ??
((error: string | Error) => {
let message = 'ra.notification.http_error';
if (!submissionErrors) {
message =
typeof error === 'string' ? error : error.message || message;
// Notification will be handled by the useNotifyIsFormInvalid hook.
if (submissionErrors && mutationMode === 'pessimistic') {
return;
}
const message =
typeof error === 'string'
? error
: error.message || 'ra.notification.http_error';
let errorMessage;
if (typeof error === 'string') {
errorMessage = error;
Expand All @@ -116,15 +120,13 @@ const useOnSubmit = ({
},
{},
);
if (submissionErrors) {
return submissionErrors;
}
return {};
return submissionErrors ?? {};
}
},
[
fields,
id,
mutationMode,
mutationOptions,
notify,
redirect,
Expand Down
Loading