Skip to content

Commit b03dfb4

Browse files
committed
Fix reference fields and inputs
1 parent d0586a1 commit b03dfb4

File tree

11 files changed

+174
-75
lines changed

11 files changed

+174
-75
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export const useReferenceArrayInputController = <
6363
isFetching: isFetchingGetMany,
6464
isPaused: isPausedGetMany,
6565
isPending: isPendingGetMany,
66+
isPlaceholderData: isPlaceholderDataGetMany,
6667
refetch: refetchGetMany,
6768
} = useGetManyAggregate<RecordType>(
6869
reference,
@@ -102,6 +103,7 @@ export const useReferenceArrayInputController = <
102103
isFetching: isFetchingGetList,
103104
isPaused: isPausedGetList,
104105
isPending: isPendingGetList,
106+
isPlaceholderData: isPlaceholderDataGetList,
105107
refetch: refetchGetMatching,
106108
} = useGetList<RecordType>(
107109
reference,
@@ -157,6 +159,7 @@ export const useReferenceArrayInputController = <
157159
isLoading: isLoadingGetMany || isLoadingGetList,
158160
isPaused: isPausedGetMany || isPausedGetList,
159161
isPending: isPendingGetMany || isPendingGetList,
162+
isPlaceholderData: isPlaceholderDataGetMany || isPlaceholderDataGetList,
160163
page: params.page,
161164
perPage: params.perPage,
162165
refetch,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type ChoicesContextBaseValue<RecordType extends RaRecord = any> = {
2121
isFetching: boolean;
2222
isLoading: boolean;
2323
isPaused: boolean;
24+
isPlaceholderData: boolean;
2425
page: number;
2526
perPage: number;
2627
refetch: (() => void) | UseGetListHookValue<RecordType>['refetch'];

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export const useChoicesContext = <ChoicesType extends RaRecord = RaRecord>(
2121
isLoading: options.isLoading ?? false,
2222
isPending: options.isPending ?? false,
2323
isFetching: options.isFetching ?? false,
24+
isPaused: options.isPaused ?? false,
25+
isPlaceholderData: options.isPlaceholderData ?? false,
2426
error: options.error,
2527
// When not in a ChoicesContext, paginating does not make sense (e.g. AutocompleteInput).
2628
perPage: Infinity,
@@ -44,6 +46,8 @@ export const useChoicesContext = <ChoicesType extends RaRecord = RaRecord>(
4446
isLoading: list.isLoading ?? false, // we must take the one for useList, otherwise the loading state isn't synchronized with the data
4547
isPending: list.isPending ?? false, // same
4648
isFetching: list.isFetching ?? false, // same
49+
isPaused: list.isPaused ?? false, // same
50+
isPlaceholderData: list.isPlaceholderData ?? false, // same
4751
page: options.page ?? list.page,
4852
perPage: options.perPage ?? list.perPage,
4953
refetch: options.refetch ?? list.refetch,

packages/ra-ui-materialui/src/Labeled.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import type { ElementType, ReactElement } from 'react';
2+
import type { ElementType, ReactElement, ReactNode } from 'react';
33
import {
44
Stack,
55
type StackProps,
@@ -45,6 +45,14 @@ export const Labeled = (inProps: LabeledProps) => {
4545
...rest
4646
} = props;
4747

48+
const childrenProps = React.isValidElement(children) ? children.props : {};
49+
const isLabeled = React.isValidElement(children)
50+
? // @ts-ignore
51+
children.type?.displayName === 'Labeled'
52+
: false;
53+
const shouldAddLabel =
54+
label !== false && childrenProps.label !== false && !isLabeled;
55+
4856
return (
4957
<Root
5058
// @ts-ignore https://github.com/mui/material-ui/issues/29875
@@ -54,13 +62,7 @@ export const Labeled = (inProps: LabeledProps) => {
5462
})}
5563
{...rest}
5664
>
57-
{label !== false &&
58-
children.props.label !== false &&
59-
typeof children.type !== 'string' &&
60-
// @ts-ignore
61-
children.type?.displayName !== 'Labeled' &&
62-
// @ts-ignore
63-
children.type?.displayName !== 'Labeled' ? (
65+
{shouldAddLabel ? (
6466
<Typography
6567
sx={
6668
color
@@ -75,8 +77,8 @@ export const Labeled = (inProps: LabeledProps) => {
7577
{...TypographyProps}
7678
>
7779
<FieldTitle
78-
label={label || children.props.label}
79-
source={source || children.props.source}
80+
label={label || childrenProps.label}
81+
source={source || childrenProps.source}
8082
resource={resource}
8183
isRequired={isRequired}
8284
/>
@@ -90,7 +92,7 @@ export const Labeled = (inProps: LabeledProps) => {
9092
Labeled.displayName = 'Labeled';
9193

9294
export interface LabeledProps extends StackProps {
93-
children: ReactElement;
95+
children: ReactNode;
9496
className?: string;
9597
color?:
9698
| ResponsiveStyleValue<Property.Color | Property.Color[]>

packages/ra-ui-materialui/src/field/ReferenceArrayField.stories.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ export const Offline = () => (
108108
);
109109

110110
export const OfflineWithChildren = () => (
111-
<AdminContext dataProvider={dataProvider} defaultTheme="light">
111+
<AdminContext
112+
dataProvider={dataProvider}
113+
i18nProvider={polyglotI18nProvider(() => englishMessages)}
114+
defaultTheme="light"
115+
>
112116
<ResourceDefinitionContextProvider definitions={resouceDefs}>
113117
<Show resource="bands" id={1} sx={{ width: 600 }}>
114118
<SimpleShowLayout>
@@ -117,11 +121,13 @@ export const OfflineWithChildren = () => (
117121
<ReferenceArrayField
118122
source="members"
119123
reference="artists"
124+
perPage={5}
120125
>
121126
<Datagrid bulkActionButtons={false}>
122127
<TextField source="id" />
123128
<TextField source="name" />
124129
</Datagrid>
130+
<Pagination />
125131
</ReferenceArrayField>
126132
</LoadChildrenOnDemand>
127133
</SimpleShowLayout>

packages/ra-ui-materialui/src/field/ReferenceManyField.stories.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from 'ra-core';
99
import { Admin, ListGuesser, Resource } from 'react-admin';
1010
import type { AdminProps } from 'react-admin';
11-
import { ThemeProvider, Box, Stack } from '@mui/material';
11+
import { ThemeProvider, Box, Stack, Typography } from '@mui/material';
1212
import { createTheme } from '@mui/material/styles';
1313
import fakeDataProvider from 'ra-data-fakerest';
1414
import polyglotI18nProvider from 'ra-i18n-polyglot';
@@ -123,6 +123,40 @@ export const Basic = () => (
123123
</Wrapper>
124124
);
125125

126+
const LoadChildrenOnDemand = ({ children }: { children: React.ReactNode }) => {
127+
const [showChildren, setShowChildren] = React.useState(false);
128+
const handleClick = () => {
129+
setShowChildren(true);
130+
};
131+
return showChildren ? (
132+
children
133+
) : (
134+
<div>
135+
<Typography variant="body2" gutterBottom>
136+
Don't forget to go offline first
137+
</Typography>
138+
<button onClick={handleClick}>Load Children</button>
139+
</div>
140+
);
141+
};
142+
143+
export const Offline = () => (
144+
<Wrapper record={authors[3]}>
145+
<LoadChildrenOnDemand>
146+
<ReferenceManyField
147+
reference="books"
148+
target="author_id"
149+
pagination={<Pagination />}
150+
perPage={5}
151+
>
152+
<Datagrid>
153+
<TextField source="title" />
154+
</Datagrid>
155+
</ReferenceManyField>
156+
</LoadChildrenOnDemand>
157+
</Wrapper>
158+
);
159+
126160
export const WithSingleFieldList = () => (
127161
<Wrapper>
128162
<ReferenceManyField reference="books" target="author_id">

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

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {
4848
import type { CommonInputProps } from './CommonInputProps';
4949
import { InputHelperText } from './InputHelperText';
5050
import { sanitizeInputRestProps } from './sanitizeInputRestProps';
51+
import { Offline } from '../Offline';
5152

5253
const defaultFilterOptions = createFilterOptions();
5354

@@ -161,13 +162,15 @@ export const AutocompleteInput = <
161162
isRequired: isRequiredOverride,
162163
label,
163164
limitChoicesToValue,
165+
loadingText = 'ra.message.loading',
164166
matchSuggestion,
165167
margin,
166168
fieldState: fieldStateOverride,
167169
filterToQuery: filterToQueryProp = DefaultFilterToQuery,
168170
formState: formStateOverride,
169171
multiple = false,
170172
noOptionsText,
173+
offline = defaultOffline,
171174
onBlur,
172175
onChange,
173176
onCreate,
@@ -197,6 +200,8 @@ export const AutocompleteInput = <
197200
const {
198201
allChoices,
199202
isPending,
203+
isPaused,
204+
isPlaceholderData,
200205
error: fetchError,
201206
resource,
202207
source,
@@ -610,12 +615,22 @@ If you provided a React element for the optionText prop, you must also provide t
610615
const renderHelperText = !!fetchError || helperText !== false || invalid;
611616

612617
const handleInputRef = useForkRef(field.ref, TextFieldProps?.inputRef);
618+
613619
return (
614620
<>
615621
<StyledAutocomplete
616622
className={clsx('ra-input', `ra-input-${source}`, className)}
617623
clearText={translate(clearText, { _: clearText })}
618624
closeText={translate(closeText, { _: closeText })}
625+
loadingText={
626+
isPaused && isPlaceholderData
627+
? offline
628+
: typeof loadingText === 'string'
629+
? translate(loadingText, {
630+
_: loadingText,
631+
})
632+
: loadingText
633+
}
619634
openOnFocus
620635
openText={translate(openText, { _: openText })}
621636
id={id}
@@ -717,17 +732,20 @@ If you provided a React element for the optionText prop, you must also provide t
717732
handleHomeEndKeys={!!create || !!onCreate}
718733
filterOptions={filterOptions}
719734
options={
720-
shouldRenderSuggestions == undefined || // eslint-disable-line eqeqeq
721-
shouldRenderSuggestions(filterValue)
722-
? suggestions
723-
: []
735+
isPaused && isPlaceholderData
736+
? []
737+
: shouldRenderSuggestions == undefined || // eslint-disable-line eqeqeq
738+
shouldRenderSuggestions(filterValue)
739+
? suggestions
740+
: []
724741
}
725742
getOptionLabel={getOptionLabel}
726743
inputValue={filterValue}
727744
loading={
728-
isPending &&
729-
(!finalChoices || finalChoices.length === 0) &&
730-
oneSecondHasPassed
745+
(isPending &&
746+
(!finalChoices || finalChoices.length === 0) &&
747+
oneSecondHasPassed) ||
748+
(isPaused && isPlaceholderData)
731749
}
732750
value={selectedChoice}
733751
onChange={handleAutocompleteChange}
@@ -798,6 +816,7 @@ export interface AutocompleteInputProps<
798816
emptyValue?: any;
799817
filterToQuery?: (searchText: string) => any;
800818
inputText?: (option: any) => string;
819+
offline?: ReactNode;
801820
onChange?: (
802821
// We can't know upfront what the value type will be
803822
value: Multiple extends true ? any[] : any,
@@ -912,6 +931,7 @@ const areSelectedItemsEqual = (
912931
};
913932

914933
const DefaultFilterToQuery = searchText => ({ q: searchText });
934+
const defaultOffline = <Offline variant="inline" />;
915935

916936
declare module '@mui/material/styles' {
917937
interface ComponentNameToClassKey {

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

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import * as React from 'react';
2-
import {
3-
DataProvider,
4-
Form,
5-
testDataProvider,
6-
TestMemoryRouter,
7-
} from 'ra-core';
2+
import { DataProvider, Form, TestMemoryRouter } from 'ra-core';
83
import polyglotI18nProvider from 'ra-i18n-polyglot';
94
import englishMessages from 'ra-language-english';
105
import { Admin, Resource } from 'react-admin';
@@ -29,23 +24,21 @@ const tags = [
2924
{ id: 2, name: 'Design' },
3025
{ id: 3, name: 'Painting' },
3126
{ id: 4, name: 'Photography' },
27+
{ id: 5, name: 'Sculpture' },
28+
{ id: 6, name: 'Urbanism' },
29+
{ id: 7, name: 'Video' },
30+
{ id: 8, name: 'Web' },
31+
{ id: 9, name: 'Writing' },
32+
{ id: 10, name: 'Other' },
3233
];
3334

34-
const dataProvider = testDataProvider({
35-
// @ts-ignore
36-
getList: () =>
37-
Promise.resolve({
38-
data: tags,
39-
total: tags.length,
40-
}),
41-
// @ts-ignore
42-
getMany: (resource, params) => {
43-
console.log('getMany', resource, params);
44-
return Promise.resolve({
45-
data: params.ids.map(id => tags.find(tag => tag.id === id)),
46-
});
35+
const dataProvider = fakeRestProvider(
36+
{
37+
tags,
4738
},
48-
});
39+
process.env.NODE_ENV !== 'test',
40+
process.env.NODE_ENV !== 'test' ? 300 : 0
41+
);
4942

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

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,13 @@ export const ReferenceArrayInput = (props: ReferenceArrayInputProps) => {
100100
const { isPaused, allChoices } = controllerProps;
101101

102102
return isPaused && allChoices == null ? (
103-
offline ?? (
104-
<Labeled
105-
source={props.source}
106-
label={props.label}
107-
resource={props.resource}
108-
>
109-
<Offline variant="inline" />
110-
</Labeled>
111-
)
103+
<Labeled
104+
source={props.source}
105+
label={props.label}
106+
resource={props.resource}
107+
>
108+
{offline ?? <Offline variant="inline" />}
109+
</Labeled>
112110
) : (
113111
<ResourceContextProvider value={reference}>
114112
<ChoicesContextProvider value={controllerProps}>

0 commit comments

Comments
 (0)