Skip to content

Commit

Permalink
Merge pull request #8183 from marmelab/fir-referenceinput-fetch-error
Browse files Browse the repository at this point in the history
Fix ReferenceInput fetching error makes AutocompleteInput unusable
  • Loading branch information
WiXSL authored Sep 22, 2022
2 parents ecbd4b1 + 65c9128 commit 9c99fed
Show file tree
Hide file tree
Showing 14 changed files with 393 additions and 58 deletions.
2 changes: 1 addition & 1 deletion packages/ra-core/src/form/choices/useChoicesContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const useChoicesContext = <ChoicesType extends RaRecord = RaRecord>(
selectedChoices: options.selectedChoices ?? data,
displayedFilters:
options.selectedChoices ?? list.displayedFilters,
error: options.error ?? list.displayedFilters,
error: options.error,
filter: options.filter ?? list.filter,
filterValues: options.filterValues ?? list.filterValues,
hasNextPage: options.hasNextPage ?? list.hasNextPage,
Expand Down
10 changes: 7 additions & 3 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export const AutocompleteInput = <
const {
allChoices,
isLoading,
error: fetchError,
resource,
source,
setFilters,
Expand Down Expand Up @@ -489,11 +490,14 @@ If you provided a React element for the optionText prop, you must also provide t
}
/>
}
error={(isTouched || isSubmitted) && invalid}
error={
!!fetchError ||
((isTouched || isSubmitted) && invalid)
}
helperText={
<InputHelperText
touched={isTouched || isSubmitted}
error={error?.message}
touched={isTouched || isSubmitted || fetchError}
error={error?.message || fetchError?.message}
helperText={helperText}
/>
}
Expand Down
16 changes: 11 additions & 5 deletions packages/ra-ui-materialui/src/input/CheckboxGroupInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,13 @@ export const CheckboxGroupInput: FunctionComponent<CheckboxGroupInputProps> = pr
...rest
} = props;

const { allChoices, isLoading, resource, source } = useChoicesContext({
const {
allChoices,
isLoading,
error: fetchError,
resource,
source,
} = useChoicesContext({
choices: choicesProp,
isFetching: isFetchingProp,
isLoading: isLoadingProp,
Expand Down Expand Up @@ -199,7 +205,7 @@ export const CheckboxGroupInput: FunctionComponent<CheckboxGroupInputProps> = pr
<StyledFormControl
component="fieldset"
margin={margin}
error={(isTouched || isSubmitted) && invalid}
error={fetchError || ((isTouched || isSubmitted) && invalid)}
className={clsx('ra-input', `ra-input-${source}`, className)}
{...sanitizeRestProps(rest)}
>
Expand Down Expand Up @@ -229,10 +235,10 @@ export const CheckboxGroupInput: FunctionComponent<CheckboxGroupInputProps> = pr
/>
))}
</FormGroup>
<FormHelperText>
<FormHelperText error={fetchError || (isTouched && !!error)}>
<InputHelperText
touched={isTouched || isSubmitted}
error={error?.message}
touched={isTouched || isSubmitted || fetchError}
error={error?.message || fetchError?.message}
helperText={helperText}
/>
</FormHelperText>
Expand Down
17 changes: 13 additions & 4 deletions packages/ra-ui-materialui/src/input/DatagridInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const DatagridInput = (props: DatagridInputProps) => {
allChoices,
availableChoices,
selectedChoices,
error: fetchError,
source,
...choicesContext
} = useChoicesContext({
Expand Down Expand Up @@ -139,11 +140,19 @@ export const DatagridInput = (props: DatagridInputProps) => {
</>
)
) : null}
<Datagrid {...rest} />
{pagination !== false && pagination}
{!fieldState.error && !fetchError && (
<>
<Datagrid {...rest} />
{pagination !== false && pagination}
</>
)}
<InputHelperText
touched={fieldState.isTouched || formState.isSubmitted}
error={fieldState.error?.message}
touched={
fieldState.isTouched ||
formState.isSubmitted ||
fetchError
}
error={fieldState.error?.message || fetchError?.message}
/>
</ListContextProvider>
</div>
Expand Down
14 changes: 10 additions & 4 deletions packages/ra-ui-materialui/src/input/RadioButtonGroupInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,13 @@ export const RadioButtonGroupInput = (props: RadioButtonGroupInputProps) => {
...rest
} = props;

const { allChoices, isLoading, resource, source } = useChoicesContext({
const {
allChoices,
isLoading,
error: fetchError,
resource,
source,
} = useChoicesContext({
choices: choicesProp,
isFetching: isFetchingProp,
isLoading: isLoadingProp,
Expand Down Expand Up @@ -157,7 +163,7 @@ export const RadioButtonGroupInput = (props: RadioButtonGroupInputProps) => {
component="fieldset"
className={clsx('ra-input', `ra-input-${source}`, className)}
margin={margin}
error={(isTouched || isSubmitted) && invalid}
error={fetchError || ((isTouched || isSubmitted) && invalid)}
{...sanitizeRestProps(rest)}
>
<FormLabel
Expand Down Expand Up @@ -191,8 +197,8 @@ export const RadioButtonGroupInput = (props: RadioButtonGroupInputProps) => {
</RadioGroup>
<FormHelperText>
<InputHelperText
touched={isTouched || isSubmitted}
error={error?.message}
touched={isTouched || isSubmitted || fetchError}
error={error?.message || fetchError?.message}
helperText={helperText}
/>
</FormHelperText>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { SimpleForm } from '../form';
import { DatagridInput } from './DatagridInput';
import { TextField } from '../field';
import { ReferenceArrayInput } from './ReferenceArrayInput';
import { SelectArrayInput } from './SelectArrayInput';
import { QueryClient } from 'react-query';

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

it('should display an error if error is defined', async () => {
jest.spyOn(console, 'error').mockImplementation(() => {});
const MyComponent = () => <span id="mycomponent" />;
render(
<AdminContext
queryClient={
Expand All @@ -43,13 +43,13 @@ describe('<ReferenceArrayInput />', () => {
>
<SimpleForm onSubmit={jest.fn()}>
<ReferenceArrayInput {...defaultProps}>
<MyComponent />
<SelectArrayInput optionText="name" />
</ReferenceArrayInput>
</SimpleForm>
</AdminContext>
);
await waitFor(() => {
expect(screen.queryByDisplayValue('fetch error')).not.toBeNull();
expect(screen.queryByText('fetch error')).not.toBeNull();
});
});
it('should pass the correct resource down to child component', async () => {
Expand Down
133 changes: 132 additions & 1 deletion packages/ra-ui-materialui/src/input/ReferenceArrayInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { AdminContext } from '../AdminContext';
import { DatagridInput } from '../input';
import { TextField } from '../field';
import { ReferenceArrayInput } from './ReferenceArrayInput';
import { AutocompleteArrayInput } from './AutocompleteArrayInput';
import { SelectArrayInput } from './SelectArrayInput';
import { CheckboxGroupInput } from './CheckboxGroupInput';
import polyglotI18nProvider from 'ra-i18n-polyglot';
import englishMessages from 'ra-language-english';

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

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

export const WithDatagridChild = () => (
export const WithAutocompleteInput = () => (
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
<ReferenceArrayInput
reference="tags"
resource="posts"
source="tag_ids"
>
<AutocompleteArrayInput optionText="name" />
</ReferenceArrayInput>
</Form>
</AdminContext>
);

export const ErrorAutocomplete = () => (
<AdminContext
dataProvider={{
getList: () => Promise.reject(new Error('fetch error')),
getMany: () =>
Promise.resolve({ data: [{ id: 5, name: 'test1' }] }),
}}
i18nProvider={i18nProvider}
>
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
<ReferenceArrayInput
reference="tags"
resource="posts"
source="tag_ids"
>
<AutocompleteArrayInput optionText="name" />
</ReferenceArrayInput>
</Form>
</AdminContext>
);

export const WithSelectArrayInput = () => (
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
<ReferenceArrayInput
reference="tags"
resource="posts"
source="tag_ids"
>
<SelectArrayInput optionText="name" />
</ReferenceArrayInput>
</Form>
</AdminContext>
);

export const ErrorSelectArray = () => (
<AdminContext
dataProvider={{
getList: () => Promise.reject(new Error('fetch error')),
getMany: () =>
Promise.resolve({ data: [{ id: 5, name: 'test1' }] }),
}}
i18nProvider={i18nProvider}
>
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
<ReferenceArrayInput
reference="tags"
resource="posts"
source="tag_ids"
>
<SelectArrayInput optionText="name" />
</ReferenceArrayInput>
</Form>
</AdminContext>
);

export const WithCheckboxGroupInput = () => (
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
<ReferenceArrayInput
reference="tags"
resource="posts"
source="tag_ids"
>
<CheckboxGroupInput optionText="name" />
</ReferenceArrayInput>
</Form>
</AdminContext>
);

export const ErrorCheckboxGroupInput = () => (
<AdminContext
dataProvider={{
getList: () => Promise.reject(new Error('fetch error')),
getMany: () =>
Promise.resolve({ data: [{ id: 5, name: 'test1' }] }),
}}
i18nProvider={i18nProvider}
>
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
<ReferenceArrayInput
reference="tags"
resource="posts"
source="tag_ids"
>
<CheckboxGroupInput optionText="name" />
</ReferenceArrayInput>
</Form>
</AdminContext>
);

export const WithDatagridInput = () => (
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
<ReferenceArrayInput
reference="tags"
resource="posts"
source="tag_ids"
>
<DatagridInput rowClick="toggleSelection" sx={{ mt: 6 }}>
<TextField source="name" />
</DatagridInput>
</ReferenceArrayInput>
</Form>
</AdminContext>
);

export const ErrorDatagridInput = () => (
<AdminContext
dataProvider={{
getList: () => Promise.reject(new Error('fetch error')),
getMany: () =>
Promise.resolve({ data: [{ id: 5, name: 'test1' }] }),
}}
i18nProvider={i18nProvider}
>
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
<ReferenceArrayInput
reference="tags"
Expand Down
9 changes: 1 addition & 8 deletions packages/ra-ui-materialui/src/input/ReferenceArrayInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
ResourceContextProvider,
ChoicesContextProvider,
} from 'ra-core';
import { ReferenceError } from './ReferenceError';

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

const controllerProps = useReferenceArrayInputController(props);

// This is not a form error but an unrecoverable error from the
// useReferenceInputController hook
if (controllerProps.error) {
return <ReferenceError label={label} error={controllerProps.error} />;
}

return (
<ResourceContextProvider value={reference}>
<ChoicesContextProvider value={controllerProps}>
Expand Down
2 changes: 1 addition & 1 deletion packages/ra-ui-materialui/src/input/ReferenceError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const ReferenceError = ({
error
disabled
label={label}
value={error?.message}
helperText={error?.message}
margin="normal"
/>
);
Expand Down
8 changes: 3 additions & 5 deletions packages/ra-ui-materialui/src/input/ReferenceInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('<ReferenceInput />', () => {
jest.spyOn(console, 'error')
.mockImplementationOnce(() => {})
.mockImplementationOnce(() => {});
const MyComponent = () => <span id="mycomponent" />;

render(
<AdminContext
queryClient={
Expand All @@ -33,14 +33,12 @@ describe('<ReferenceInput />', () => {
})}
>
<SimpleForm onSubmit={jest.fn()}>
<ReferenceInput {...defaultProps}>
<MyComponent />
</ReferenceInput>
<ReferenceInput {...defaultProps} />
</SimpleForm>
</AdminContext>
);
await waitFor(() => {
expect(screen.queryByDisplayValue('fetch error')).not.toBeNull();
expect(screen.queryByText('fetch error')).not.toBeNull();
});
});

Expand Down
Loading

0 comments on commit 9c99fed

Please sign in to comment.