Skip to content

Commit 33cb2a3

Browse files
committed
Add offline support to <ReferenceInputBase>, <ReferenceInput> and <AutocompleteInput>
1 parent 2f737ed commit 33cb2a3

File tree

10 files changed

+375
-60
lines changed

10 files changed

+375
-60
lines changed

docs/AutocompleteInput.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ The form value for the source must be the selected value, e.g.
6767
| `isPending` | Optional | `boolean` | `false` | If `true`, the component will display a loading indicator. |
6868
| `inputText` | Optional | `Function` | `-` | Required if `optionText` is a custom Component, this function must return the text displayed for the current selection. |
6969
| `matchSuggestion` | Optional | `Function` | `-` | Required if `optionText` is a React element. Function returning a boolean indicating whether a choice matches the filter. `(filter, choice) => boolean` |
70+
| `offline` | Optional | `ReactNode` | - | What to render when there is no network connectivity when fetching the choices |
7071
| `onChange` | Optional | `Function` | `-` | A function called with the new value, along with the selected record, when the input value changes |
7172
| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
7273
| `optionText` | Optional | `string` &#124; `Function` &#124; `Component` | `undefined` &#124; `record Representation` | Field name of record to display in the suggestion item or function using the choice object as argument |
@@ -340,6 +341,27 @@ const UserCountry = () => {
340341
}
341342
```
342343

344+
## `offline`
345+
346+
`<AutocompleteInput>` can display a custom message when it can't fetch the choices because there is no network connectivity, thanks to the `offline` prop.
347+
348+
```jsx
349+
<ReferenceInput source="user_id" reference="users">
350+
<AutocompleteInput offline={<span>No network, could not fetch data</span>} />
351+
</ReferenceInput>
352+
```
353+
354+
You can pass either a React element or a string to the `offline` prop:
355+
356+
```jsx
357+
<ReferenceInput source="user_id" reference="users">
358+
<AutocompleteInput offline={<span>No network, could not fetch data</span>} />
359+
</ReferenceInput>
360+
<ReferenceInput source="user_id" reference="users">
361+
<AutocompleteInput offline="No network, could not fetch data" />
362+
</ReferenceInput>
363+
```
364+
343365
## `onChange`
344366

345367
Use the `onChange` prop to get notified when the input value changes.

docs/ReferenceInput.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ See the [`children`](#children) section for more details.
110110
| `label` | Optional | `string` | - | Useful only when `ReferenceInput` is in a Filter array, the label is used as the Filter label. |
111111
| `page` | Optional | `number` | 1 | The current page number |
112112
| `perPage` | Optional | `number` | 25 | Number of suggestions to show |
113+
| `offline` | Optional | `ReactNode` | - | What to render when there is no network connectivity when loading the record |
113114
| `queryOptions` | Optional | [`UseQueryOptions`](https://tanstack.com/query/v5/docs/react/reference/useQuery) | `{}` | `react-query` client options |
114115
| `sort` | Optional | `{ field: String, order: 'ASC' or 'DESC' }` | `{ field:'id', order:'DESC' }` | How to order the list of suggestions |
115116

@@ -191,6 +192,26 @@ const filters = [
191192
];
192193
```
193194

195+
## `offline`
196+
197+
`<ReferenceInput>` can display a custom message when the referenced record is missing because there is no network connectivity, thanks to the `offline` prop.
198+
199+
```jsx
200+
<ReferenceInput source="user_id" reference="users" offline="No network, could not fetch data" />
201+
```
202+
203+
`<ReferenceInput>` renders the `offline` element when:
204+
205+
- the referenced record is missing (no record in the `users` table with the right `user_id`), and
206+
- there is no network connectivity
207+
208+
You can pass either a React element or a string to the `offline` prop:
209+
210+
```jsx
211+
<ReferenceInput source="user_id" reference="users" offline={<span>No network, could not fetch data</span>} />
212+
<ReferenceInput source="user_id" reference="users" offline="No network, could not fetch data" />
213+
```
214+
194215
## `parse`
195216

196217
By default, children of `<ReferenceInput>` transform the empty form value (an empty string) into `null` before passing it to the `dataProvider`.

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
SelfReference,
1818
QueryOptions,
1919
Meta,
20+
Offline,
2021
} from './ReferenceInputBase.stories';
2122

2223
describe('<ReferenceInputBase />', () => {
@@ -68,8 +69,8 @@ describe('<ReferenceInputBase />', () => {
6869
return <div>{resource}</div>;
6970
};
7071
const dataProvider = testDataProvider({
71-
// @ts-ignore
7272
getList: () =>
73+
// @ts-ignore
7374
Promise.resolve({ data: [{ id: 1 }, { id: 2 }], total: 2 }),
7475
});
7576
render(
@@ -92,8 +93,8 @@ describe('<ReferenceInputBase />', () => {
9293
return <div aria-label="total">{total}</div>;
9394
};
9495
const dataProvider = testDataProvider({
95-
// @ts-ignore
9696
getList: () =>
97+
// @ts-ignore
9798
Promise.resolve({ data: [{ id: 1 }, { id: 2 }], total: 2 }),
9899
});
99100
render(
@@ -187,6 +188,15 @@ describe('<ReferenceInputBase />', () => {
187188
screen.getByText('Save').click();
188189
await screen.findByText('Proust', undefined, { timeout: 5000 });
189190
});
191+
192+
it('should render the offline prop node when offline', async () => {
193+
render(<Offline />);
194+
fireEvent.click(await screen.findByText('Simulate offline'));
195+
fireEvent.click(await screen.findByText('Toggle Child'));
196+
await screen.findByText('You are offline, cannot load data');
197+
fireEvent.click(await screen.findByText('Simulate online'));
198+
await screen.findByText('lorem');
199+
});
190200
});
191201

192202
const AutocompleteInput = (

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

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { QueryClient } from '@tanstack/react-query';
2+
import { onlineManager, QueryClient } from '@tanstack/react-query';
33
import polyglotI18nProvider from 'ra-i18n-polyglot';
44
import englishMessages from 'ra-language-english';
55
import fakeRestDataProvider from 'ra-data-fakerest';
@@ -22,6 +22,7 @@ import {
2222
ListBase,
2323
Resource,
2424
testDataProvider,
25+
useIsOffline,
2526
useListContext,
2627
useRedirect,
2728
} from '../..';
@@ -213,7 +214,7 @@ const dataProvider = testDataProvider({
213214
data: tags,
214215
total: tags.length,
215216
}),
216-
1500
217+
process.env.NODE_ENV === 'test' ? 0 : 1500
217218
);
218219
}),
219220
// @ts-ignore
@@ -522,3 +523,54 @@ export const Meta = ({
522523
</CoreAdmin>
523524
</TestMemoryRouter>
524525
);
526+
527+
export const Offline = () => (
528+
<CoreAdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
529+
<Form onSubmit={() => {}} defaultValues={{ tag_ids: [5] }}>
530+
<div
531+
style={{
532+
width: '200px',
533+
display: 'flex',
534+
flexDirection: 'column',
535+
gap: '10px',
536+
}}
537+
>
538+
<RenderChildOnDemand>
539+
<ReferenceInputBase
540+
reference="tags"
541+
resource="posts"
542+
source="tag_ids"
543+
offline={<p>You are offline, cannot load data</p>}
544+
>
545+
<SelectInput optionText="name" />
546+
</ReferenceInputBase>
547+
</RenderChildOnDemand>
548+
<SimulateOfflineButton />
549+
</div>
550+
</Form>
551+
</CoreAdminContext>
552+
);
553+
554+
const SimulateOfflineButton = () => {
555+
const isOffline = useIsOffline();
556+
return (
557+
<button
558+
type="button"
559+
onClick={() => onlineManager.setOnline(isOffline)}
560+
>
561+
{isOffline ? 'Simulate online' : 'Simulate offline'}
562+
</button>
563+
);
564+
};
565+
566+
const RenderChildOnDemand = ({ children }) => {
567+
const [showChild, setShowChild] = React.useState(false);
568+
return (
569+
<>
570+
<button onClick={() => setShowChild(!showChild)}>
571+
Toggle Child
572+
</button>
573+
{showChild && <div>{children}</div>}
574+
</>
575+
);
576+
};

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

Lines changed: 11 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,
7374
} = props;
7475

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

82+
const { isPaused, allChoices } = controllerProps;
83+
84+
const shouldRenderOffline =
85+
isPaused &&
86+
allChoices == null &&
87+
offline !== false &&
88+
offline !== undefined;
89+
8190
return (
8291
<ResourceContextProvider value={reference}>
8392
<ChoicesContextProvider value={controllerProps}>
84-
{children}
93+
{shouldRenderOffline ? offline : children}
8594
</ChoicesContextProvider>
8695
</ResourceContextProvider>
8796
);
@@ -91,4 +100,5 @@ export interface ReferenceInputBaseProps
91100
extends InputProps,
92101
UseReferenceInputControllerParams {
93102
children?: ReactNode;
103+
offline?: ReactNode;
94104
}

0 commit comments

Comments
 (0)