Skip to content

Commit 32af617

Browse files
committed
Add support for offline in Reference inputs
1 parent 934fff4 commit 32af617

File tree

8 files changed

+140
-8
lines changed

8 files changed

+140
-8
lines changed

packages/ra-core/src/controller/input/ReferenceInputBase.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const ReferenceInputBase = (props: ReferenceInputBaseProps) => {
7070
reference,
7171
sort = { field: 'id', order: 'DESC' },
7272
filter = {},
73+
offline = null,
7374
} = props;
7475

7576
const controllerProps = useReferenceInputController({
@@ -78,7 +79,9 @@ export const ReferenceInputBase = (props: ReferenceInputBaseProps) => {
7879
filter,
7980
});
8081

81-
return (
82+
return controllerProps.isPaused ? (
83+
offline
84+
) : (
8285
<ResourceContextProvider value={reference}>
8386
<ChoicesContextProvider value={controllerProps}>
8487
{children}
@@ -91,4 +94,5 @@ export interface ReferenceInputBaseProps
9194
extends InputProps,
9295
UseReferenceInputControllerParams {
9396
children?: ReactNode;
97+
offline?: ReactNode;
9498
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export const useReferenceArrayInputController = <
6161
error: errorGetMany,
6262
isLoading: isLoadingGetMany,
6363
isFetching: isFetchingGetMany,
64+
isPaused: isPausedGetMany,
6465
isPending: isPendingGetMany,
6566
refetch: refetchGetMany,
6667
} = useGetManyAggregate<RecordType>(
@@ -99,6 +100,7 @@ export const useReferenceArrayInputController = <
99100
error: errorGetList,
100101
isLoading: isLoadingGetList,
101102
isFetching: isFetchingGetList,
103+
isPaused: isPausedGetList,
102104
isPending: isPendingGetList,
103105
refetch: refetchGetMatching,
104106
} = useGetList<RecordType>(
@@ -153,6 +155,7 @@ export const useReferenceArrayInputController = <
153155
hideFilter: paramsModifiers.hideFilter,
154156
isFetching: isFetchingGetMany || isFetchingGetList,
155157
isLoading: isLoadingGetMany || isLoadingGetList,
158+
isPaused: isPausedGetMany || isPausedGetList,
156159
isPending: isPendingGetMany || isPendingGetList,
157160
page: params.page,
158161
perPage: params.perPage,

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const useReferenceInputController = <RecordType extends RaRecord = any>(
8484
pageInfo,
8585
isFetching: isFetchingPossibleValues,
8686
isLoading: isLoadingPossibleValues,
87+
isPaused: isPausedPossibleValues,
8788
isPending: isPendingPossibleValues,
8889
error: errorPossibleValues,
8990
refetch: refetchGetList,
@@ -112,6 +113,7 @@ export const useReferenceInputController = <RecordType extends RaRecord = any>(
112113
error: errorReference,
113114
isLoading: isLoadingReference,
114115
isFetching: isFetchingReference,
116+
isPaused: isPausedReference,
115117
isPending: isPendingReference,
116118
} = useReference<RecordType>({
117119
id: currentValue,
@@ -128,6 +130,7 @@ export const useReferenceInputController = <RecordType extends RaRecord = any>(
128130
// The reference query isn't enabled when there is no value yet but as it has no data, react-query will flag it as pending
129131
(currentValue != null && currentValue !== '' && isPendingReference) ||
130132
isPendingPossibleValues;
133+
const isPaused = isPausedPossibleValues || isPausedReference;
131134

132135
// We need to delay the update of the referenceRecord and the finalData
133136
// to the next React state update, because otherwise it can raise a warning
@@ -176,7 +179,8 @@ export const useReferenceInputController = <RecordType extends RaRecord = any>(
176179
hideFilter: paramsModifiers.hideFilter,
177180
isFetching: isFetchingReference || isFetchingPossibleValues,
178181
isLoading: isLoadingReference || isLoadingPossibleValues,
179-
isPending: isPending,
182+
isPaused,
183+
isPending,
180184
page: params.page,
181185
perPage: params.perPage,
182186
refetch,

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

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

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { ReferenceArrayInput } from './ReferenceArrayInput';
1919
import { AutocompleteArrayInput } from './AutocompleteArrayInput';
2020
import { SelectArrayInput } from './SelectArrayInput';
2121
import { CheckboxGroupInput } from './CheckboxGroupInput';
22+
import { Typography } from '@mui/material';
2223

2324
export default { title: 'ra-ui-materialui/input/ReferenceArrayInput' };
2425

@@ -74,6 +75,51 @@ export const Basic = () => (
7475
</TestMemoryRouter>
7576
);
7677

78+
const LoadChildrenOnDemand = ({ children }: { children: React.ReactNode }) => {
79+
const [showChildren, setShowChildren] = React.useState(false);
80+
const handleClick = () => {
81+
setShowChildren(true);
82+
};
83+
return showChildren ? (
84+
children
85+
) : (
86+
<div>
87+
<Typography variant="body2" gutterBottom>
88+
Don't forget to go offline first
89+
</Typography>
90+
<button onClick={handleClick}>Load Children</button>
91+
</div>
92+
);
93+
};
94+
95+
export const Offline = () => (
96+
<TestMemoryRouter initialEntries={['/posts/create']}>
97+
<Admin dataProvider={dataProvider}>
98+
<Resource name="tags" recordRepresentation={'name'} />
99+
<Resource
100+
name="posts"
101+
create={() => (
102+
<Create
103+
resource="posts"
104+
record={{ tags_ids: [1, 3] }}
105+
sx={{ width: 600 }}
106+
>
107+
<SimpleForm>
108+
<LoadChildrenOnDemand>
109+
<ReferenceArrayInput
110+
reference="tags"
111+
resource="posts"
112+
source="tags_ids"
113+
/>
114+
</LoadChildrenOnDemand>
115+
</SimpleForm>
116+
</Create>
117+
)}
118+
/>
119+
</Admin>
120+
</TestMemoryRouter>
121+
);
122+
77123
export const WithAutocompleteInput = () => (
78124
<AdminContext
79125
dataProvider={dataProvider}

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { ReactElement } from 'react';
2+
import type { ReactNode } from 'react';
33
import {
44
InputProps,
55
useReferenceArrayInputController,
@@ -8,6 +8,8 @@ import {
88
UseReferenceArrayInputParams,
99
} from 'ra-core';
1010
import { AutocompleteArrayInput } from './AutocompleteArrayInput';
11+
import { Labeled } from '../Labeled';
12+
import { Offline } from '../Offline';
1113

1214
/**
1315
* An Input component for fields containing a list of references to another resource.
@@ -82,6 +84,7 @@ export const ReferenceArrayInput = (props: ReferenceArrayInputProps) => {
8284
reference,
8385
sort,
8486
filter = defaultFilter,
87+
offline,
8588
} = props;
8689
if (React.Children.count(children) !== 1) {
8790
throw new Error(
@@ -95,7 +98,13 @@ export const ReferenceArrayInput = (props: ReferenceArrayInputProps) => {
9598
filter,
9699
});
97100

98-
return (
101+
return controllerProps.isPaused ? (
102+
offline ?? (
103+
<Labeled {...props}>
104+
<Offline />
105+
</Labeled>
106+
)
107+
) : (
99108
<ResourceContextProvider value={reference}>
100109
<ChoicesContextProvider value={controllerProps}>
101110
{children}
@@ -110,7 +119,8 @@ const defaultFilter = {};
110119
export interface ReferenceArrayInputProps
111120
extends InputProps,
112121
UseReferenceArrayInputParams {
113-
children?: ReactElement;
122+
children?: ReactNode;
114123
label?: string;
124+
offline?: ReactNode;
115125
[key: string]: any;
116126
}

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,54 @@ export const Basic = ({ dataProvider = dataProviderWithAuthors }) => (
109109
</TestMemoryRouter>
110110
);
111111

112+
const LoadChildrenOnDemand = ({ children }: { children: React.ReactNode }) => {
113+
const [showChildren, setShowChildren] = React.useState(false);
114+
const handleClick = () => {
115+
setShowChildren(true);
116+
};
117+
return showChildren ? (
118+
children
119+
) : (
120+
<div>
121+
<Typography variant="body2" gutterBottom>
122+
Don't forget to go offline first
123+
</Typography>
124+
<button onClick={handleClick}>Load Children</button>
125+
</div>
126+
);
127+
};
128+
129+
const BookEditOffline = () => (
130+
<Edit
131+
mutationMode="pessimistic"
132+
mutationOptions={{
133+
onSuccess: data => {
134+
console.log(data);
135+
},
136+
}}
137+
>
138+
<SimpleForm>
139+
<LoadChildrenOnDemand>
140+
<ReferenceInput reference="authors" source="author" />
141+
</LoadChildrenOnDemand>
142+
</SimpleForm>
143+
</Edit>
144+
);
145+
146+
export const Offline = ({ dataProvider = dataProviderWithAuthors }) => (
147+
<TestMemoryRouter initialEntries={['/books/1']}>
148+
<Admin dataProvider={dataProvider}>
149+
<Resource
150+
name="authors"
151+
recordRepresentation={record =>
152+
`${record.first_name} ${record.last_name}`
153+
}
154+
/>
155+
<Resource name="books" edit={BookEditOffline} />
156+
</Admin>
157+
</TestMemoryRouter>
158+
);
159+
112160
const tags = [
113161
{ id: 5, name: 'lorem' },
114162
{ id: 6, name: 'ipsum' },

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import React from 'react';
1+
import React, { ReactNode } from 'react';
22
import { ReferenceInputBase, ReferenceInputBaseProps } from 'ra-core';
33

44
import { AutocompleteInput } from './AutocompleteInput';
5+
import { Offline } from '../Offline';
6+
import { Labeled } from '../Labeled';
57

68
/**
79
* An Input component for choosing a reference record. Useful for foreign keys.
@@ -64,15 +66,28 @@ import { AutocompleteInput } from './AutocompleteInput';
6466
* a `setFilters` function. You can call this function to filter the results.
6567
*/
6668
export const ReferenceInput = (props: ReferenceInputProps) => {
67-
const { children = defaultChildren, ...rest } = props;
69+
const { children = defaultChildren, offline, ...rest } = props;
6870

6971
if (props.validate && process.env.NODE_ENV !== 'production') {
7072
throw new Error(
7173
'<ReferenceInput> does not accept a validate prop. Set the validate prop on the child instead.'
7274
);
7375
}
7476

75-
return <ReferenceInputBase {...rest}>{children}</ReferenceInputBase>;
77+
return (
78+
<ReferenceInputBase
79+
{...rest}
80+
offline={
81+
offline ?? (
82+
<Labeled {...rest}>
83+
<Offline />
84+
</Labeled>
85+
)
86+
}
87+
>
88+
{children}
89+
</ReferenceInputBase>
90+
);
7691
};
7792

7893
const defaultChildren = <AutocompleteInput />;
@@ -82,5 +97,6 @@ export interface ReferenceInputProps extends ReferenceInputBaseProps {
8297
* Call validate on the child component instead
8398
*/
8499
validate?: never;
100+
offline?: ReactNode;
85101
[key: string]: any;
86102
}

0 commit comments

Comments
 (0)