Skip to content

Commit 8908c54

Browse files
Fix Create and Edit forms shows "Server communication error" instead of the root error message in case of validation error (#658)
* ListGuesserProps extends with UseResourceDefinitionOptions to fix missing props * skip validation errors notification handling * notification handling on validation errors * skip on pessimistic mode only
1 parent 72a0749 commit 8908c54

File tree

5 files changed

+87
-12
lines changed

5 files changed

+87
-12
lines changed

src/create/CreateGuesser.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export const IntrospectedCreateGuesser = ({
5252
schema,
5353
schemaAnalyzer,
5454
resource,
55+
mutationMode = 'pessimistic',
5556
mutationOptions,
5657
redirect: redirectTo = 'list',
5758
mode,
@@ -70,6 +71,7 @@ export const IntrospectedCreateGuesser = ({
7071
resource,
7172
schemaAnalyzer,
7273
fields,
74+
mutationMode,
7375
mutationOptions,
7476
transform,
7577
redirectTo,

src/edit/EditGuesser.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export const IntrospectedEditGuesser = ({
7676
schemaAnalyzer,
7777
fields,
7878
mutationOptions,
79+
mutationMode,
7980
transform,
8081
redirectTo,
8182
children: [],

src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,4 +525,5 @@ export type UseOnSubmitProps = Pick<
525525
'schemaAnalyzer' | 'resource' | 'fields'
526526
> &
527527
Pick<CreateProps, 'mutationOptions' | 'transform'> &
528-
PickRename<CreateProps, 'redirect', 'redirectTo'>;
528+
PickRename<CreateProps, 'redirect', 'redirectTo'> &
529+
Partial<Pick<EditProps, 'mutationMode'>>;

src/useOnSubmit.test.tsx

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@ import * as React from 'react';
22
import { jest } from '@jest/globals';
33
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
44
import { render, waitFor } from '@testing-library/react';
5-
import type { CreateResult, RaRecord, UpdateResult } from 'react-admin';
6-
import { DataProviderContext, testDataProvider } from 'react-admin';
5+
import {
6+
type CreateResult,
7+
DataProviderContext,
8+
type MutationMode,
9+
type RaRecord,
10+
type UpdateResult,
11+
testDataProvider,
12+
} from 'react-admin';
713
import { MemoryRouter, Route, Routes } from 'react-router-dom';
814

9-
import useOnSubmit from './useOnSubmit.js';
1015
import schemaAnalyzer from './hydra/schemaAnalyzer.js';
1116
import { API_FIELDS_DATA } from './__fixtures__/parsedData.js';
1217

@@ -23,6 +28,16 @@ const onSubmitProps = {
2328
};
2429

2530
jest.mock('./getIdentifierValue.js');
31+
const notify = jest.fn();
32+
const reactAdminActual = jest.requireActual('react-admin') as Record<
33+
string,
34+
unknown
35+
>;
36+
jest.mock('react-admin', () => ({
37+
__esModule: true,
38+
...reactAdminActual,
39+
useNotify: () => notify,
40+
}));
2641

2742
test.each([
2843
{
@@ -41,6 +56,7 @@ test.each([
4156
])(
4257
'Call create with file input ($name)',
4358
async (values: Omit<RaRecord, 'id'>) => {
59+
const { default: useOnSubmit } = await import('./useOnSubmit.js');
4460
let save;
4561
const Dummy = () => {
4662
const onSubmit = useOnSubmit(onSubmitProps);
@@ -93,6 +109,7 @@ test.each([
93109
cover: null,
94110
},
95111
])('Call update without file inputs ($name)', async (values: RaRecord) => {
112+
const { default: useOnSubmit } = await import('./useOnSubmit.js');
96113
let save;
97114
const Dummy = () => {
98115
const onSubmit = useOnSubmit(onSubmitProps);
@@ -125,3 +142,55 @@ test.each([
125142
});
126143
});
127144
});
145+
146+
test.each`
147+
submissionErrors | mutationMode | shouldNotify
148+
${{ name: 'Required' }} | ${'pessimistic'} | ${false}
149+
${{ name: 'Required' }} | ${'optimistic'} | ${true}
150+
${{ name: 'Required' }} | ${'undoable'} | ${true}
151+
${null} | ${'pessimistic'} | ${true}
152+
`(
153+
'notification handling on validation errors ($submissionErrors, $mutationMode)',
154+
async ({ submissionErrors, mutationMode, shouldNotify }) => {
155+
const { default: useOnSubmit } = await import('./useOnSubmit.js');
156+
notify.mockClear();
157+
dataProvider.create = jest.fn(() =>
158+
Promise.reject(new Error('Service Unavailable')),
159+
);
160+
161+
let save;
162+
const Dummy = () => {
163+
const onSubmit = useOnSubmit({
164+
...onSubmitProps,
165+
mutationMode: mutationMode as MutationMode,
166+
schemaAnalyzer: {
167+
...onSubmitProps.schemaAnalyzer,
168+
getSubmissionErrors: () => submissionErrors,
169+
},
170+
});
171+
save = onSubmit;
172+
return <span />;
173+
};
174+
175+
render(
176+
<DataProviderContext.Provider value={dataProvider}>
177+
<QueryClientProvider client={new QueryClient()}>
178+
<MemoryRouter initialEntries={['/books/create']}>
179+
<Routes>
180+
<Route path="/books/create" element={<Dummy />} />
181+
</Routes>
182+
</MemoryRouter>
183+
</QueryClientProvider>
184+
</DataProviderContext.Provider>,
185+
);
186+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
187+
// @ts-ignore
188+
const errors = await save({ author: 'Author 1' });
189+
190+
await waitFor(() => {
191+
expect(dataProvider.create).toHaveBeenCalled();
192+
});
193+
(shouldNotify ? expect(notify) : expect(notify).not).toHaveBeenCalled();
194+
expect(errors).toEqual(submissionErrors ?? {});
195+
},
196+
);

src/useOnSubmit.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const useOnSubmit = ({
2121
schemaAnalyzer,
2222
fields,
2323
mutationOptions,
24+
mutationMode = 'pessimistic',
2425
transform,
2526
redirectTo = 'list',
2627
}: UseOnSubmitProps): ((
@@ -92,11 +93,14 @@ const useOnSubmit = ({
9293
const failure =
9394
mutationOptions?.onError ??
9495
((error: string | Error) => {
95-
let message = 'ra.notification.http_error';
96-
if (!submissionErrors) {
97-
message =
98-
typeof error === 'string' ? error : error.message || message;
96+
// Notification will be handled by the useNotifyIsFormInvalid hook.
97+
if (submissionErrors && mutationMode === 'pessimistic') {
98+
return;
9999
}
100+
const message =
101+
typeof error === 'string'
102+
? error
103+
: error.message || 'ra.notification.http_error';
100104
let errorMessage;
101105
if (typeof error === 'string') {
102106
errorMessage = error;
@@ -116,15 +120,13 @@ const useOnSubmit = ({
116120
},
117121
{},
118122
);
119-
if (submissionErrors) {
120-
return submissionErrors;
121-
}
122-
return {};
123+
return submissionErrors ?? {};
123124
}
124125
},
125126
[
126127
fields,
127128
id,
129+
mutationMode,
128130
mutationOptions,
129131
notify,
130132
redirect,

0 commit comments

Comments
 (0)