Skip to content

Commit 577b0ae

Browse files
committed
Add offline support to <ReferenceManyFieldBase>
1 parent 33cb2a3 commit 577b0ae

File tree

6 files changed

+227
-94
lines changed

6 files changed

+227
-94
lines changed

docs/ReferenceManyFieldBase.md

Lines changed: 67 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export const PostList = () => (
9494
| `debounce` | Optional\* | `number` | 500 | debounce time in ms for the `setFilters` callbacks |
9595
| `empty` | Optional | `ReactNode` | - | Element to display when there are no related records. |
9696
| `filter` | Optional | `Object` | - | Filters to use when fetching the related records, passed to `getManyReference()` |
97+
| `offline` | Optional | `ReactNode` | - | Element to display when there are no related records because of lack of network connectivity. |
9798
| `perPage` | Optional | `number` | 25 | Maximum number of referenced records to fetch |
9899
| `queryOptions` | Optional | [`UseQuery Options`](https://tanstack.com/query/v3/docs/react/reference/useQuery) | `{}` | `react-query` options for the `getMany` query |
99100
| `reference` | Required | `string` | - | The name of the resource for the referenced records, e.g. 'books' |
@@ -135,45 +136,6 @@ export const AuthorShow = () => (
135136
);
136137
```
137138
138-
## `render`
139-
140-
Alternatively, you can pass a `render` function prop instead of children. The `render` prop will receive the `ListContext` as arguments, allowing to inline the render logic.
141-
When receiving a `render` function prop the `<ReferenceManyFieldBase>` component will ignore the children property.
142-
143-
```jsx
144-
import { ShowBase, ReferenceManyFieldBase } from 'react-admin';
145-
146-
const AuthorShow = () => (
147-
<ShowBase>
148-
<ReferenceManyFieldBase
149-
reference="books"
150-
target="author_id"
151-
render={
152-
({ isPending, error, data }) => {
153-
154-
if (isPending) {
155-
return <p>Loading...</p>;
156-
}
157-
158-
if (error) {
159-
return <p className="error">{error.toString()}</p>;
160-
}
161-
return (
162-
<ul>
163-
{data.map((book, index) => (
164-
<li key={index}>
165-
<i>{book.title}</i>, published on{' '}{book.published_at}
166-
</li>
167-
))}
168-
</ul>
169-
);
170-
}
171-
}
172-
/>
173-
</ShowBase>
174-
);
175-
```
176-
177139
## `debounce`
178140
179141
By default, `<ReferenceManyFieldBase>` does not refresh the data as soon as the user enters data in the filter form. Instead, it waits for half a second of user inactivity (via `lodash.debounce`) before calling the `dataProvider` on filter change. This is to prevent repeated (and useless) calls to the API.
@@ -191,7 +153,7 @@ const PostCommentsField = () => (
191153
192154
## `empty`
193155
194-
Use `empty` to customize the text displayed when the related record is empty.
156+
Use `empty` to customize the text displayed when there are no related records.
195157
196158
```jsx
197159
<ReferenceManyFieldBase
@@ -203,18 +165,6 @@ Use `empty` to customize the text displayed when the related record is empty.
203165
</ReferenceManyFieldBase>
204166
```
205167
206-
`empty` also accepts a translation key.
207-
208-
```jsx
209-
<ReferenceManyFieldBase
210-
reference="books"
211-
target="author_id"
212-
empty="resources.authors.fields.books.empty"
213-
>
214-
...
215-
</ReferenceManyFieldBase>
216-
```
217-
218168
`empty` also accepts a `ReactNode`.
219169
220170
```jsx
@@ -245,6 +195,32 @@ You can filter the query used to populate the possible values. Use the `filter`
245195
246196
{% endraw %}
247197
198+
## `offline`
199+
200+
Use `offline` to customize the text displayed when there are no related records because of lack of network connectivity.
201+
202+
```jsx
203+
<ReferenceManyFieldBase
204+
reference="books"
205+
target="author_id"
206+
offline="Offline, could not load data"
207+
>
208+
...
209+
</ReferenceManyFieldBase>
210+
```
211+
212+
`offline` also accepts a `ReactNode`.
213+
214+
```jsx
215+
<ReferenceManyFieldBase
216+
reference="books"
217+
target="author_id"
218+
empty={<p>Offline, could not load data</p>}
219+
>
220+
...
221+
</ReferenceManyFieldBase>
222+
```
223+
248224
## `perPage`
249225
250226
By default, react-admin restricts the possible values to 25 and displays no pagination control. You can change the limit by setting the `perPage` prop:
@@ -281,6 +257,45 @@ For instance, if you want to display the `books` of a given `author`, the `refer
281257
</ReferenceManyFieldBase>
282258
```
283259
260+
## `render`
261+
262+
Alternatively, you can pass a `render` function prop instead of children. The `render` prop will receive the `ListContext` as arguments, allowing to inline the render logic.
263+
When receiving a `render` function prop the `<ReferenceManyFieldBase>` component will ignore the children property.
264+
265+
```jsx
266+
import { ShowBase, ReferenceManyFieldBase } from 'react-admin';
267+
268+
const AuthorShow = () => (
269+
<ShowBase>
270+
<ReferenceManyFieldBase
271+
reference="books"
272+
target="author_id"
273+
render={
274+
({ isPending, error, data }) => {
275+
276+
if (isPending) {
277+
return <p>Loading...</p>;
278+
}
279+
280+
if (error) {
281+
return <p className="error">{error.toString()}</p>;
282+
}
283+
return (
284+
<ul>
285+
{data.map((book, index) => (
286+
<li key={index}>
287+
<i>{book.title}</i>, published on{' '}{book.published_at}
288+
</li>
289+
))}
290+
</ul>
291+
);
292+
}
293+
}
294+
/>
295+
</ShowBase>
296+
);
297+
```
298+
284299
## `sort`
285300
286301
By default, it orders the possible values by id desc. You can change this order by setting the `sort` prop (an object with `field` and `order` properties).

packages/ra-core/src/controller/field/ReferenceManyFieldBase.spec.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import * as React from 'react';
2-
import { render, screen, waitFor } from '@testing-library/react';
2+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
33
import {
44
Basic,
55
Errored,
66
Loading,
7+
Offline,
78
WithRenderProp,
89
} from './ReferenceManyFieldBase.stories';
910

@@ -138,4 +139,21 @@ describe('ReferenceManyFieldBase', () => {
138139
});
139140
});
140141
});
142+
143+
it('should render the offline prop node when offline', async () => {
144+
render(<Offline offline={<p>You are offline, cannot load data</p>} />);
145+
fireEvent.click(await screen.findByText('Simulate offline'));
146+
fireEvent.click(await screen.findByText('Toggle Child'));
147+
await screen.findByText('You are offline, cannot load data');
148+
fireEvent.click(await screen.findByText('Simulate online'));
149+
await screen.findByText('War and Peace');
150+
});
151+
it('should allow children to handle the offline state', async () => {
152+
render(<Offline offline={undefined} />);
153+
fireEvent.click(await screen.findByText('Simulate offline'));
154+
fireEvent.click(await screen.findByText('Toggle Child'));
155+
await screen.findByText('AuthorList: Offline. Could not load data');
156+
fireEvent.click(await screen.findByText('Simulate online'));
157+
await screen.findByText('War and Peace');
158+
});
141159
});

packages/ra-core/src/controller/field/ReferenceManyFieldBase.stories.tsx

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import * as React from 'react';
2-
import { QueryClient } from '@tanstack/react-query';
2+
import { onlineManager, QueryClient } from '@tanstack/react-query';
33
import { CoreAdmin } from '../../core/CoreAdmin';
44
import { Resource } from '../../core/Resource';
55
import { ShowBase } from '../../controller/show/ShowBase';
66
import { TestMemoryRouter } from '../../routing';
77
import { ReferenceManyFieldBase } from './ReferenceManyFieldBase';
88
import { ListBase, ListIterator, useListContext } from '../list';
9-
import { DataTableBase } from '../../dataTable';
109
import fakeRestDataProvider from 'ra-data-fakerest';
10+
import { useIsOffline } from '../../core';
1111

1212
export default {
1313
title: 'ra-core/controller/field/ReferenceManyFieldBase',
@@ -308,13 +308,102 @@ export const WithRenderProp = ({
308308
</TestMemoryRouter>
309309
);
310310

311+
export const Offline = ({ offline }) => {
312+
return (
313+
<TestMemoryRouter initialEntries={['/authors/1/show']}>
314+
<CoreAdmin
315+
dataProvider={dataProviderWithAuthors}
316+
queryClient={
317+
new QueryClient({
318+
defaultOptions: {
319+
queries: {
320+
retry: false,
321+
},
322+
},
323+
})
324+
}
325+
>
326+
<Resource name="books" />
327+
<Resource
328+
name="authors"
329+
show={
330+
<ShowBase>
331+
<RenderChildOnDemand>
332+
<ReferenceManyFieldBase
333+
target="author"
334+
source="id"
335+
reference="books"
336+
offline={offline}
337+
>
338+
<AuthorList source="title" />
339+
</ReferenceManyFieldBase>
340+
</RenderChildOnDemand>
341+
<p>
342+
<SimulateOfflineButton />
343+
</p>
344+
</ShowBase>
345+
}
346+
/>
347+
</CoreAdmin>
348+
</TestMemoryRouter>
349+
);
350+
};
351+
352+
Offline.args = {
353+
offline: 'let children handle offline state',
354+
};
355+
356+
Offline.argTypes = {
357+
offline: {
358+
control: { type: 'radio' },
359+
options: [
360+
'let children handle offline state',
361+
'handle offline state in ReferenceManyFieldBase',
362+
],
363+
mapping: {
364+
'let children handle offline state': undefined,
365+
'handle offline state in ReferenceManyFieldBase': (
366+
<p>You are offline, cannot load data</p>
367+
),
368+
},
369+
},
370+
};
371+
372+
const SimulateOfflineButton = () => {
373+
const isOffline = useIsOffline();
374+
return (
375+
<button
376+
type="button"
377+
onClick={() => onlineManager.setOnline(isOffline)}
378+
>
379+
{isOffline ? 'Simulate online' : 'Simulate offline'}
380+
</button>
381+
);
382+
};
383+
384+
const RenderChildOnDemand = ({ children }) => {
385+
const [showChild, setShowChild] = React.useState(false);
386+
return (
387+
<>
388+
<button onClick={() => setShowChild(!showChild)}>
389+
Toggle Child
390+
</button>
391+
{showChild && <div>{children}</div>}
392+
</>
393+
);
394+
};
395+
311396
const AuthorList = ({ source }) => {
312-
const { isPending, error, data } = useListContext();
397+
const { isPaused, isPending, error, data } = useListContext();
313398

314-
if (isPending) {
399+
if (isPending && !isPaused) {
315400
return <p>Loading...</p>;
316401
}
317402

403+
if (isPaused) {
404+
return <p>AuthorList: Offline. Could not load data</p>;
405+
}
406+
318407
if (error) {
319408
return <p style={{ color: 'red' }}>{error.toString()}</p>;
320409
}

0 commit comments

Comments
 (0)