Skip to content

Commit 9f6e81a

Browse files
authored
Merge pull request #5767 from marmelab/fix-reference-input-loading
Refactor ReferenceInput to let its child handle loading
2 parents f93b89d + d041937 commit 9f6e81a

20 files changed

+513
-97
lines changed

packages/ra-core/src/controller/input/ReferenceInputController.spec.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ describe('<ReferenceInputController />', () => {
214214
choices: [{ id: 1 }],
215215
error: null,
216216
filter: { q: '' },
217-
loading: false,
217+
loaded: false,
218+
loading: true,
218219
pagination: { page: 1, perPage: 25 },
219220
sort: { field: 'title', order: 'ASC' },
220221
warning: null,

packages/ra-core/src/controller/input/useReferenceInputController.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,8 @@ export const useReferenceInputController = (
202202
// kept for backwards compatibility
203203
// @deprecated to be removed in 4.0
204204
error: dataStatus.error,
205-
loading: dataStatus.waiting,
205+
loading: possibleValuesLoading || referenceLoading,
206+
loaded: possibleValuesLoaded && referenceLoaded,
206207
filter: filterValues,
207208
setFilter,
208209
pagination,
@@ -231,6 +232,7 @@ export interface ReferenceInputValue {
231232
};
232233
choices: Record[];
233234
error?: string;
235+
loaded: boolean;
234236
loading: boolean;
235237
pagination: PaginationPayload;
236238
setFilter: (filter: string) => void;

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

+17
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { useFieldArray } from 'react-final-form-arrays';
66
import { InputLabel, FormControl } from '@material-ui/core';
77

88
import sanitizeInputRestProps from './sanitizeInputRestProps';
9+
import Labeled from './Labeled';
10+
import { LinearProgress } from '../layout';
911

1012
/**
1113
* To edit arrays of data embedded inside a record, <ArrayInput> creates a list of sub-forms.
@@ -52,6 +54,8 @@ const ArrayInput: FC<ArrayInputProps> = ({
5254
className,
5355
defaultValue,
5456
label,
57+
loaded,
58+
loading,
5559
children,
5660
record,
5761
resource,
@@ -71,6 +75,19 @@ const ArrayInput: FC<ArrayInputProps> = ({
7175
...rest,
7276
});
7377

78+
if (loading) {
79+
return (
80+
<Labeled
81+
label={label}
82+
source={source}
83+
resource={resource}
84+
className={className}
85+
>
86+
<LinearProgress />
87+
</Labeled>
88+
);
89+
}
90+
7491
return (
7592
<FormControl
7693
fullWidth

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

+61
Original file line numberDiff line numberDiff line change
@@ -662,4 +662,65 @@ describe('<AutocompleteArrayInput />', () => {
662662
fireEvent.focus(input);
663663
expect(queryAllByRole('option')).toHaveLength(1);
664664
});
665+
666+
// TODO: restore once master has been merged back to next
667+
it.skip('should not render a LinearProgress if loading is true and a second has not passed yet', () => {
668+
const { queryByRole } = render(
669+
<Form
670+
validateOnBlur
671+
onSubmit={jest.fn()}
672+
render={() => (
673+
<AutocompleteArrayInput
674+
{...{
675+
...defaultProps,
676+
loaded: true,
677+
loading: true,
678+
}}
679+
/>
680+
)}
681+
/>
682+
);
683+
684+
expect(queryByRole('progressbar')).toBeNull();
685+
});
686+
687+
it('should render a LinearProgress if loading is true and a second has passed', async () => {
688+
const { queryByRole } = render(
689+
<Form
690+
validateOnBlur
691+
onSubmit={jest.fn()}
692+
render={() => (
693+
<AutocompleteArrayInput
694+
{...{
695+
...defaultProps,
696+
loaded: true,
697+
loading: true,
698+
}}
699+
/>
700+
)}
701+
/>
702+
);
703+
704+
await new Promise(resolve => setTimeout(resolve, 1001));
705+
706+
expect(queryByRole('progressbar')).not.toBeNull();
707+
});
708+
709+
it('should not render a LinearProgress if loading is false', () => {
710+
const { queryByRole } = render(
711+
<Form
712+
validateOnBlur
713+
onSubmit={jest.fn()}
714+
render={() => (
715+
<AutocompleteArrayInput
716+
{...{
717+
...defaultProps,
718+
}}
719+
/>
720+
)}
721+
/>
722+
);
723+
724+
expect(queryByRole('progressbar')).toBeNull();
725+
});
665726
});

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

+6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
import InputHelperText from './InputHelperText';
2424
import AutocompleteSuggestionList from './AutocompleteSuggestionList';
2525
import AutocompleteSuggestionItem from './AutocompleteSuggestionItem';
26+
import { AutocompleteInputLoader } from './AutocompleteInputLoader';
2627

2728
interface Options {
2829
suggestionsContainerProps?: any;
@@ -110,6 +111,8 @@ const AutocompleteArrayInput: FunctionComponent<
110111
input: inputOverride,
111112
isRequired: isRequiredOverride,
112113
label,
114+
loaded,
115+
loading,
113116
limitChoicesToValue,
114117
margin = 'dense',
115118
matchSuggestion,
@@ -410,6 +413,9 @@ const AutocompleteArrayInput: FunctionComponent<
410413
))}
411414
</div>
412415
),
416+
endAdornment: loading && (
417+
<AutocompleteInputLoader />
418+
),
413419
onBlur,
414420
onChange: event => {
415421
handleFilterChange(event);

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

+61
Original file line numberDiff line numberDiff line change
@@ -639,4 +639,65 @@ describe('<AutocompleteInput />', () => {
639639
expect(queryByDisplayValue('foo')).not.toBeNull();
640640
});
641641
});
642+
643+
// TODO: restore once master has been merged back to next
644+
it.skip('should not render a LinearProgress if loading is true and a second has not passed yet', () => {
645+
const { queryByRole } = render(
646+
<Form
647+
validateOnBlur
648+
onSubmit={jest.fn()}
649+
render={() => (
650+
<AutocompleteInput
651+
{...{
652+
...defaultProps,
653+
loaded: true,
654+
loading: true,
655+
}}
656+
/>
657+
)}
658+
/>
659+
);
660+
661+
expect(queryByRole('progressbar')).toBeNull();
662+
});
663+
664+
it('should render a LinearProgress if loading is true and a second has passed', async () => {
665+
const { queryByRole } = render(
666+
<Form
667+
validateOnBlur
668+
onSubmit={jest.fn()}
669+
render={() => (
670+
<AutocompleteInput
671+
{...{
672+
...defaultProps,
673+
loaded: true,
674+
loading: true,
675+
}}
676+
/>
677+
)}
678+
/>
679+
);
680+
681+
await new Promise(resolve => setTimeout(resolve, 1001));
682+
683+
expect(queryByRole('progressbar')).not.toBeNull();
684+
});
685+
686+
it('should not render a LinearProgress if loading is false', () => {
687+
const { queryByRole } = render(
688+
<Form
689+
validateOnBlur
690+
onSubmit={jest.fn()}
691+
render={() => (
692+
<AutocompleteInput
693+
{...{
694+
...defaultProps,
695+
}}
696+
/>
697+
)}
698+
/>
699+
);
700+
701+
expect(queryByRole('progressbar')).toBeNull();
702+
});
642703
});

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

+14-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
import InputHelperText from './InputHelperText';
2727
import AutocompleteSuggestionList from './AutocompleteSuggestionList';
2828
import AutocompleteSuggestionItem from './AutocompleteSuggestionItem';
29+
import { AutocompleteInputLoader } from './AutocompleteInputLoader';
2930

3031
interface Options {
3132
suggestionsContainerProps?: any;
@@ -112,6 +113,8 @@ const AutocompleteInput: FunctionComponent<AutocompleteInputProps> = props => {
112113
isRequired: isRequiredOverride,
113114
label,
114115
limitChoicesToValue,
116+
loaded,
117+
loading,
115118
margin = 'dense',
116119
matchSuggestion,
117120
meta: metaOverride,
@@ -355,7 +358,12 @@ const AutocompleteInput: FunctionComponent<AutocompleteInputProps> = props => {
355358

356359
const getEndAdornment = openMenu => {
357360
if (!resettable) {
358-
return endAdornment;
361+
if (endAdornment) {
362+
return endAdornment;
363+
}
364+
if (loading) {
365+
return <AutocompleteInputLoader />;
366+
}
359367
} else if (!filterValue) {
360368
const label = translate('ra.action.clear_input_value');
361369
if (clearAlwaysVisible) {
@@ -376,6 +384,7 @@ const AutocompleteInput: FunctionComponent<AutocompleteInputProps> = props => {
376384
)}
377385
/>
378386
</IconButton>
387+
{loading && <AutocompleteInputLoader />}
379388
</InputAdornment>
380389
);
381390
} else {
@@ -386,6 +395,7 @@ const AutocompleteInput: FunctionComponent<AutocompleteInputProps> = props => {
386395
return (
387396
<InputAdornment position="end">
388397
<span className={classes.clearButton}>&nbsp;</span>
398+
{loading && <AutocompleteInputLoader />}
389399
</InputAdornment>
390400
);
391401
}
@@ -411,6 +421,7 @@ const AutocompleteInput: FunctionComponent<AutocompleteInputProps> = props => {
411421
})}
412422
/>
413423
</IconButton>
424+
{loading && <AutocompleteInputLoader />}
414425
</InputAdornment>
415426
);
416427
}
@@ -586,6 +597,8 @@ export interface AutocompleteInputProps
586597
Omit<DownshiftProps<any>, 'onChange'> {
587598
clearAlwaysVisible?: boolean;
588599
resettable?: boolean;
600+
loaded?: boolean;
601+
loading?: boolean;
589602
}
590603

591604
export default AutocompleteInput;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react';
2+
import { CircularProgress } from '@material-ui/core';
3+
import { useTimeout } from 'ra-core';
4+
5+
export const AutocompleteInputLoader = ({ timeout = 1000 }) => {
6+
const oneSecondHasPassed = useTimeout(timeout);
7+
8+
if (oneSecondHasPassed) {
9+
return <CircularProgress size={24} />;
10+
}
11+
12+
return null;
13+
};

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

+61
Original file line numberDiff line numberDiff line change
@@ -267,4 +267,65 @@ describe('<CheckboxGroupInput />', () => {
267267
expect(error.classList.contains('Mui-error')).toEqual(true);
268268
});
269269
});
270+
271+
// TODO: restore once master has been merged back to next
272+
it.skip('should not render a LinearProgress if loading is true and a second has not passed yet', () => {
273+
const { queryByRole } = render(
274+
<Form
275+
validateOnBlur
276+
onSubmit={jest.fn()}
277+
render={() => (
278+
<CheckboxGroupInput
279+
{...{
280+
...defaultProps,
281+
loaded: true,
282+
loading: true,
283+
}}
284+
/>
285+
)}
286+
/>
287+
);
288+
289+
expect(queryByRole('progressbar')).toBeNull();
290+
});
291+
292+
it('should render a LinearProgress if loading is true and a second has passed', async () => {
293+
const { queryByRole } = render(
294+
<Form
295+
validateOnBlur
296+
onSubmit={jest.fn()}
297+
render={() => (
298+
<CheckboxGroupInput
299+
{...{
300+
...defaultProps,
301+
loaded: true,
302+
loading: true,
303+
}}
304+
/>
305+
)}
306+
/>
307+
);
308+
309+
await new Promise(resolve => setTimeout(resolve, 1001));
310+
311+
expect(queryByRole('progressbar')).not.toBeNull();
312+
});
313+
314+
it('should not render a LinearProgress if loading is false', () => {
315+
const { queryByRole } = render(
316+
<Form
317+
validateOnBlur
318+
onSubmit={jest.fn()}
319+
render={() => (
320+
<CheckboxGroupInput
321+
{...{
322+
...defaultProps,
323+
}}
324+
/>
325+
)}
326+
/>
327+
);
328+
329+
expect(queryByRole('progressbar')).toBeNull();
330+
});
270331
});

0 commit comments

Comments
 (0)