Skip to content

Commit 9c99fed

Browse files
authored
Merge pull request #8183 from marmelab/fir-referenceinput-fetch-error
Fix ReferenceInput fetching error makes AutocompleteInput unusable
2 parents ecbd4b1 + 65c9128 commit 9c99fed

14 files changed

+393
-58
lines changed

packages/ra-core/src/form/choices/useChoicesContext.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const useChoicesContext = <ChoicesType extends RaRecord = RaRecord>(
2323
selectedChoices: options.selectedChoices ?? data,
2424
displayedFilters:
2525
options.selectedChoices ?? list.displayedFilters,
26-
error: options.error ?? list.displayedFilters,
26+
error: options.error,
2727
filter: options.filter ?? list.filter,
2828
filterValues: options.filterValues ?? list.filterValues,
2929
hasNextPage: options.hasNextPage ?? list.hasNextPage,

packages/ra-ui-materialui/src/input/AutocompleteInput.tsx

+7-3
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ export const AutocompleteInput = <
178178
const {
179179
allChoices,
180180
isLoading,
181+
error: fetchError,
181182
resource,
182183
source,
183184
setFilters,
@@ -489,11 +490,14 @@ If you provided a React element for the optionText prop, you must also provide t
489490
}
490491
/>
491492
}
492-
error={(isTouched || isSubmitted) && invalid}
493+
error={
494+
!!fetchError ||
495+
((isTouched || isSubmitted) && invalid)
496+
}
493497
helperText={
494498
<InputHelperText
495-
touched={isTouched || isSubmitted}
496-
error={error?.message}
499+
touched={isTouched || isSubmitted || fetchError}
500+
error={error?.message || fetchError?.message}
497501
helperText={helperText}
498502
/>
499503
}

packages/ra-ui-materialui/src/input/CheckboxGroupInput.tsx

+11-5
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,13 @@ export const CheckboxGroupInput: FunctionComponent<CheckboxGroupInputProps> = pr
114114
...rest
115115
} = props;
116116

117-
const { allChoices, isLoading, resource, source } = useChoicesContext({
117+
const {
118+
allChoices,
119+
isLoading,
120+
error: fetchError,
121+
resource,
122+
source,
123+
} = useChoicesContext({
118124
choices: choicesProp,
119125
isFetching: isFetchingProp,
120126
isLoading: isLoadingProp,
@@ -199,7 +205,7 @@ export const CheckboxGroupInput: FunctionComponent<CheckboxGroupInputProps> = pr
199205
<StyledFormControl
200206
component="fieldset"
201207
margin={margin}
202-
error={(isTouched || isSubmitted) && invalid}
208+
error={fetchError || ((isTouched || isSubmitted) && invalid)}
203209
className={clsx('ra-input', `ra-input-${source}`, className)}
204210
{...sanitizeRestProps(rest)}
205211
>
@@ -229,10 +235,10 @@ export const CheckboxGroupInput: FunctionComponent<CheckboxGroupInputProps> = pr
229235
/>
230236
))}
231237
</FormGroup>
232-
<FormHelperText>
238+
<FormHelperText error={fetchError || (isTouched && !!error)}>
233239
<InputHelperText
234-
touched={isTouched || isSubmitted}
235-
error={error?.message}
240+
touched={isTouched || isSubmitted || fetchError}
241+
error={error?.message || fetchError?.message}
236242
helperText={helperText}
237243
/>
238244
</FormHelperText>

packages/ra-ui-materialui/src/input/DatagridInput.tsx

+13-4
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export const DatagridInput = (props: DatagridInputProps) => {
6464
allChoices,
6565
availableChoices,
6666
selectedChoices,
67+
error: fetchError,
6768
source,
6869
...choicesContext
6970
} = useChoicesContext({
@@ -139,11 +140,19 @@ export const DatagridInput = (props: DatagridInputProps) => {
139140
</>
140141
)
141142
) : null}
142-
<Datagrid {...rest} />
143-
{pagination !== false && pagination}
143+
{!fieldState.error && !fetchError && (
144+
<>
145+
<Datagrid {...rest} />
146+
{pagination !== false && pagination}
147+
</>
148+
)}
144149
<InputHelperText
145-
touched={fieldState.isTouched || formState.isSubmitted}
146-
error={fieldState.error?.message}
150+
touched={
151+
fieldState.isTouched ||
152+
formState.isSubmitted ||
153+
fetchError
154+
}
155+
error={fieldState.error?.message || fetchError?.message}
147156
/>
148157
</ListContextProvider>
149158
</div>

packages/ra-ui-materialui/src/input/RadioButtonGroupInput.tsx

+10-4
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,13 @@ export const RadioButtonGroupInput = (props: RadioButtonGroupInputProps) => {
106106
...rest
107107
} = props;
108108

109-
const { allChoices, isLoading, resource, source } = useChoicesContext({
109+
const {
110+
allChoices,
111+
isLoading,
112+
error: fetchError,
113+
resource,
114+
source,
115+
} = useChoicesContext({
110116
choices: choicesProp,
111117
isFetching: isFetchingProp,
112118
isLoading: isLoadingProp,
@@ -157,7 +163,7 @@ export const RadioButtonGroupInput = (props: RadioButtonGroupInputProps) => {
157163
component="fieldset"
158164
className={clsx('ra-input', `ra-input-${source}`, className)}
159165
margin={margin}
160-
error={(isTouched || isSubmitted) && invalid}
166+
error={fetchError || ((isTouched || isSubmitted) && invalid)}
161167
{...sanitizeRestProps(rest)}
162168
>
163169
<FormLabel
@@ -191,8 +197,8 @@ export const RadioButtonGroupInput = (props: RadioButtonGroupInputProps) => {
191197
</RadioGroup>
192198
<FormHelperText>
193199
<InputHelperText
194-
touched={isTouched || isSubmitted}
195-
error={error?.message}
200+
touched={isTouched || isSubmitted || fetchError}
201+
error={error?.message || fetchError?.message}
196202
helperText={helperText}
197203
/>
198204
</FormHelperText>

packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { SimpleForm } from '../form';
1313
import { DatagridInput } from './DatagridInput';
1414
import { TextField } from '../field';
1515
import { ReferenceArrayInput } from './ReferenceArrayInput';
16+
import { SelectArrayInput } from './SelectArrayInput';
1617
import { QueryClient } from 'react-query';
1718

1819
describe('<ReferenceArrayInput />', () => {
@@ -29,7 +30,6 @@ describe('<ReferenceArrayInput />', () => {
2930

3031
it('should display an error if error is defined', async () => {
3132
jest.spyOn(console, 'error').mockImplementation(() => {});
32-
const MyComponent = () => <span id="mycomponent" />;
3333
render(
3434
<AdminContext
3535
queryClient={
@@ -43,13 +43,13 @@ describe('<ReferenceArrayInput />', () => {
4343
>
4444
<SimpleForm onSubmit={jest.fn()}>
4545
<ReferenceArrayInput {...defaultProps}>
46-
<MyComponent />
46+
<SelectArrayInput optionText="name" />
4747
</ReferenceArrayInput>
4848
</SimpleForm>
4949
</AdminContext>
5050
);
5151
await waitFor(() => {
52-
expect(screen.queryByDisplayValue('fetch error')).not.toBeNull();
52+
expect(screen.queryByText('fetch error')).not.toBeNull();
5353
});
5454
});
5555
it('should pass the correct resource down to child component', async () => {

packages/ra-ui-materialui/src/input/ReferenceArrayInput.stories.tsx

+132-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { AdminContext } from '../AdminContext';
55
import { DatagridInput } from '../input';
66
import { TextField } from '../field';
77
import { ReferenceArrayInput } from './ReferenceArrayInput';
8+
import { AutocompleteArrayInput } from './AutocompleteArrayInput';
9+
import { SelectArrayInput } from './SelectArrayInput';
10+
import { CheckboxGroupInput } from './CheckboxGroupInput';
811
import polyglotI18nProvider from 'ra-i18n-polyglot';
912
import englishMessages from 'ra-language-english';
1013

@@ -29,8 +32,136 @@ const dataProvider = testDataProvider({
2932

3033
const i18nProvider = polyglotI18nProvider(() => englishMessages);
3134

32-
export const WithDatagridChild = () => (
35+
export const WithAutocompleteInput = () => (
3336
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
37+
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
38+
<ReferenceArrayInput
39+
reference="tags"
40+
resource="posts"
41+
source="tag_ids"
42+
>
43+
<AutocompleteArrayInput optionText="name" />
44+
</ReferenceArrayInput>
45+
</Form>
46+
</AdminContext>
47+
);
48+
49+
export const ErrorAutocomplete = () => (
50+
<AdminContext
51+
dataProvider={{
52+
getList: () => Promise.reject(new Error('fetch error')),
53+
getMany: () =>
54+
Promise.resolve({ data: [{ id: 5, name: 'test1' }] }),
55+
}}
56+
i18nProvider={i18nProvider}
57+
>
58+
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
59+
<ReferenceArrayInput
60+
reference="tags"
61+
resource="posts"
62+
source="tag_ids"
63+
>
64+
<AutocompleteArrayInput optionText="name" />
65+
</ReferenceArrayInput>
66+
</Form>
67+
</AdminContext>
68+
);
69+
70+
export const WithSelectArrayInput = () => (
71+
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
72+
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
73+
<ReferenceArrayInput
74+
reference="tags"
75+
resource="posts"
76+
source="tag_ids"
77+
>
78+
<SelectArrayInput optionText="name" />
79+
</ReferenceArrayInput>
80+
</Form>
81+
</AdminContext>
82+
);
83+
84+
export const ErrorSelectArray = () => (
85+
<AdminContext
86+
dataProvider={{
87+
getList: () => Promise.reject(new Error('fetch error')),
88+
getMany: () =>
89+
Promise.resolve({ data: [{ id: 5, name: 'test1' }] }),
90+
}}
91+
i18nProvider={i18nProvider}
92+
>
93+
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
94+
<ReferenceArrayInput
95+
reference="tags"
96+
resource="posts"
97+
source="tag_ids"
98+
>
99+
<SelectArrayInput optionText="name" />
100+
</ReferenceArrayInput>
101+
</Form>
102+
</AdminContext>
103+
);
104+
105+
export const WithCheckboxGroupInput = () => (
106+
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
107+
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
108+
<ReferenceArrayInput
109+
reference="tags"
110+
resource="posts"
111+
source="tag_ids"
112+
>
113+
<CheckboxGroupInput optionText="name" />
114+
</ReferenceArrayInput>
115+
</Form>
116+
</AdminContext>
117+
);
118+
119+
export const ErrorCheckboxGroupInput = () => (
120+
<AdminContext
121+
dataProvider={{
122+
getList: () => Promise.reject(new Error('fetch error')),
123+
getMany: () =>
124+
Promise.resolve({ data: [{ id: 5, name: 'test1' }] }),
125+
}}
126+
i18nProvider={i18nProvider}
127+
>
128+
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
129+
<ReferenceArrayInput
130+
reference="tags"
131+
resource="posts"
132+
source="tag_ids"
133+
>
134+
<CheckboxGroupInput optionText="name" />
135+
</ReferenceArrayInput>
136+
</Form>
137+
</AdminContext>
138+
);
139+
140+
export const WithDatagridInput = () => (
141+
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
142+
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
143+
<ReferenceArrayInput
144+
reference="tags"
145+
resource="posts"
146+
source="tag_ids"
147+
>
148+
<DatagridInput rowClick="toggleSelection" sx={{ mt: 6 }}>
149+
<TextField source="name" />
150+
</DatagridInput>
151+
</ReferenceArrayInput>
152+
</Form>
153+
</AdminContext>
154+
);
155+
156+
export const ErrorDatagridInput = () => (
157+
<AdminContext
158+
dataProvider={{
159+
getList: () => Promise.reject(new Error('fetch error')),
160+
getMany: () =>
161+
Promise.resolve({ data: [{ id: 5, name: 'test1' }] }),
162+
}}
163+
i18nProvider={i18nProvider}
164+
>
34165
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
35166
<ReferenceArrayInput
36167
reference="tags"

packages/ra-ui-materialui/src/input/ReferenceArrayInput.tsx

+1-8
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
ResourceContextProvider,
88
ChoicesContextProvider,
99
} from 'ra-core';
10-
import { ReferenceError } from './ReferenceError';
1110

1211
/**
1312
* An Input component for fields containing a list of references to another resource.
@@ -77,7 +76,7 @@ import { ReferenceError } from './ReferenceError';
7776
* a `setFilters` function. You can call this function to filter the results.
7877
*/
7978
export const ReferenceArrayInput = (props: ReferenceArrayInputProps) => {
80-
const { children, label, reference } = props;
79+
const { children, reference } = props;
8180
if (React.Children.count(children) !== 1) {
8281
throw new Error(
8382
'<ReferenceArrayInput> only accepts a single child (like <Datagrid>)'
@@ -86,12 +85,6 @@ export const ReferenceArrayInput = (props: ReferenceArrayInputProps) => {
8685

8786
const controllerProps = useReferenceArrayInputController(props);
8887

89-
// This is not a form error but an unrecoverable error from the
90-
// useReferenceInputController hook
91-
if (controllerProps.error) {
92-
return <ReferenceError label={label} error={controllerProps.error} />;
93-
}
94-
9588
return (
9689
<ResourceContextProvider value={reference}>
9790
<ChoicesContextProvider value={controllerProps}>

packages/ra-ui-materialui/src/input/ReferenceError.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const ReferenceError = ({
1414
error
1515
disabled
1616
label={label}
17-
value={error?.message}
17+
helperText={error?.message}
1818
margin="normal"
1919
/>
2020
);

packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx

+3-5
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe('<ReferenceInput />', () => {
2020
jest.spyOn(console, 'error')
2121
.mockImplementationOnce(() => {})
2222
.mockImplementationOnce(() => {});
23-
const MyComponent = () => <span id="mycomponent" />;
23+
2424
render(
2525
<AdminContext
2626
queryClient={
@@ -33,14 +33,12 @@ describe('<ReferenceInput />', () => {
3333
})}
3434
>
3535
<SimpleForm onSubmit={jest.fn()}>
36-
<ReferenceInput {...defaultProps}>
37-
<MyComponent />
38-
</ReferenceInput>
36+
<ReferenceInput {...defaultProps} />
3937
</SimpleForm>
4038
</AdminContext>
4139
);
4240
await waitFor(() => {
43-
expect(screen.queryByDisplayValue('fetch error')).not.toBeNull();
41+
expect(screen.queryByText('fetch error')).not.toBeNull();
4442
});
4543
});
4644

0 commit comments

Comments
 (0)