Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/ReferenceField.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ It uses `dataProvider.getMany()` instead of `dataProvider.getOne()` [for perform
| `empty` | Optional | `ReactNode` | - | What to render when the field has no value or when the reference is missing |
| `label` | Optional | `string | Function` | `resources. [resource]. fields.[source]` | Label to use for the field when rendered in layout components |
| `link` | Optional | `string | Function` | `edit` | Target of the link wrapping the rendered child. Set to `false` to disable the link. |
| `offline` | Optional | `ReactNode` | - | What to render when there is no network connectivity when loading the record |
| `queryOptions` | Optional | [`UseQuery Options`](https://tanstack.com/query/v5/docs/react/reference/useQuery) | `{}` | `react-query` client options |
| `sortBy` | Optional | `string | Function` | `source` | Name of the field to use for sorting when used in a Datagrid |

Expand All @@ -98,6 +99,7 @@ By default, `<ReferenceField>` renders the `recordRepresentation` of the referen
```

Alternatively, you can use [the `render` prop](#render) to render the referenced record in a custom way.

## `empty`

`<ReferenceField>` can display a custom message when the referenced record is missing, thanks to the `empty` prop.
Expand Down Expand Up @@ -172,6 +174,21 @@ You can also use a custom `link` function to get a custom path for the children.
/>
```

## `offline`

When the user is offline, `<ReferenceField>` is smart enough to display the referenced record if it was previously fetched. However, if the referenced record has never been fetched before, `<ReferenceField>` displays an error message explaining that the app has lost network connectivity.

You can customize this error message by passing a React element or a string to the `offline` prop:

```jsx
<ReferenceField source="user_id" reference="users" offline={<span>No network, could not fetch data</span>} >
...
</ReferenceField>
<ReferenceField source="user_id" reference="users" offline="No network, could not fetch data" >
...
</ReferenceField>
```

## `queryOptions`

Use the `queryOptions` prop to pass options to [the `dataProvider.getMany()` query](./useGetOne.md#aggregating-getone-calls) that fetches the referenced record.
Expand Down
75 changes: 46 additions & 29 deletions docs/ReferenceFieldBase.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ It uses `dataProvider.getMany()` instead of `dataProvider.getOne()` [for perform
| `children` | Optional | `ReactNode` | - | React component to render the referenced record. |
| `render` | Optional | `(context) => ReactNode` | - | Function that takes the referenceFieldContext and renders the referenced record. |
| `empty` | Optional | `ReactNode` | - | What to render when the field has no value or when the reference is missing |
| `offline` | Optional | `ReactNode` | - | What to render when there is no network connectivity when loading the record |
| `queryOptions` | Optional | [`UseQuery Options`](https://tanstack.com/query/v5/docs/react/reference/useQuery) | `{}` | `react-query` client options |
| `sortBy` | Optional | `string | Function` | `source` | Name of the field to use for sorting when used in a Datagrid |

Expand Down Expand Up @@ -100,35 +101,6 @@ export const MyReferenceField = () => (
);
```

## `render`

Alternatively, you can pass a `render` function prop instead of children. This function will receive the `ReferenceFieldContext` as argument.

```jsx
export const MyReferenceField = () => (
<ReferenceFieldBase
source="user_id"
reference="users"
render={({ error, isPending, referenceRecord }) => {
if (isPending) {
return <p>Loading...</p>;
}

if (error) {
return (
<p className="error">
{error.message}
</p>
);
}
return <p>{referenceRecord.name}</p>;
}}
/>
);
```

The `render` function prop will take priority on `children` props if both are set.

## `empty`

`<ReferenceFieldBase>` can display a custom message when the referenced record is missing, thanks to the `empty` prop.
Expand All @@ -155,6 +127,21 @@ You can pass either a React element or a string to the `empty` prop:
</ReferenceFieldBase>
```

## `offline`

When the user is offline, `<ReferenceFieldBase>` is smart enough to display the referenced record if it was previously fetched. However, if the referenced record has never been fetched before, `<ReferenceFieldBase>` displays an error message explaining that the app has lost network connectivity.

You can customize this error message by passing a React element or a string to the `offline` prop:

```jsx
<ReferenceFieldBase source="user_id" reference="users" offline={<span>No network, could not fetch data</span>} >
...
</ReferenceFieldBase>
<ReferenceFieldBase source="user_id" reference="users" offline="No network, could not fetch data" >
...
</ReferenceFieldBase>
```

## `queryOptions`

Use the `queryOptions` prop to pass options to [the `dataProvider.getMany()` query](./useGetOne.md#aggregating-getone-calls) that fetches the referenced record.
Expand Down Expand Up @@ -186,6 +173,36 @@ For instance, if the `posts` resource has a `user_id` field, set the `reference`
</ReferenceFieldBase>
```


## `render`

Alternatively, you can pass a `render` function prop instead of children. This function will receive the `ReferenceFieldContext` as argument.

```jsx
export const MyReferenceField = () => (
<ReferenceFieldBase
source="user_id"
reference="users"
render={({ error, isPending, referenceRecord }) => {
if (isPending) {
return <p>Loading...</p>;
}

if (error) {
return (
<p className="error">
{error.message}
</p>
);
}
return <p>{referenceRecord.name}</p>;
}}
/>
);
```

The `render` function prop will take priority on `children` props if both are set.

## `sortBy`

By default, when used in a `<Datagrid>`, and when the user clicks on the column header of a `<ReferenceFieldBase>`, react-admin sorts the list by the field `source`. To specify another field name to sort by, set the `sortBy` prop.
Expand Down
31 changes: 21 additions & 10 deletions packages/ra-core/src/controller/field/ReferenceFieldBase.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import expect from 'expect';
import { render, screen, waitFor } from '@testing-library/react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { CoreAdminContext } from '../../core/CoreAdminContext';
import { useResourceContext } from '../../core/useResourceContext';
import { testDataProvider } from '../../dataProvider';
Expand All @@ -10,8 +10,10 @@ import {
Errored,
Loading,
Meta,
Offline,
WithRenderProp,
} from './ReferenceFieldBase.stories';
import { RecordContextProvider } from '../record';

describe('<ReferenceFieldBase />', () => {
beforeAll(() => {
Expand Down Expand Up @@ -46,20 +48,20 @@ describe('<ReferenceFieldBase />', () => {
return <div>{resource}</div>;
};
const dataProvider = testDataProvider({
// @ts-ignore
getList: () =>
getMany: () =>
// @ts-ignore
Promise.resolve({ data: [{ id: 1 }, { id: 2 }], total: 2 }),
});
render(
<CoreAdminContext dataProvider={dataProvider}>
<ReferenceFieldBase reference="posts" source="post_id">
<MyComponent />
</ReferenceFieldBase>
<RecordContextProvider value={{ post_id: 1 }}>
<ReferenceFieldBase reference="posts" source="post_id">
<MyComponent />
</ReferenceFieldBase>
</RecordContextProvider>
</CoreAdminContext>
);
await waitFor(() => {
expect(screen.queryByText('posts')).not.toBeNull();
});
await screen.findByText('posts');
});

it('should accept meta in queryOptions', async () => {
Expand All @@ -70,8 +72,8 @@ describe('<ReferenceFieldBase />', () => {
);
const dataProvider = testDataProvider({
getMany,
// @ts-ignore
getOne: () =>
// @ts-ignore
Promise.resolve({
data: {
id: 1,
Expand Down Expand Up @@ -165,4 +167,13 @@ describe('<ReferenceFieldBase />', () => {
});
});
});

it('should render the offline prop node when offline', async () => {
render(<Offline />);
fireEvent.click(await screen.findByText('Simulate offline'));
fireEvent.click(await screen.findByText('Toggle Child'));
await screen.findByText('You are offline, cannot load data');
fireEvent.click(await screen.findByText('Simulate online'));
await screen.findByText('Leo');
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { QueryClient } from '@tanstack/react-query';
import { onlineManager, QueryClient } from '@tanstack/react-query';
import fakeRestDataProvider from 'ra-data-fakerest';
import { CoreAdmin } from '../../core/CoreAdmin';
import { Resource } from '../../core/Resource';
Expand All @@ -9,6 +9,7 @@ import { ReferenceFieldBase } from './ReferenceFieldBase';
import { useFieldValue } from '../../util/useFieldValue';
import { useReferenceFieldContext } from './ReferenceFieldContext';
import { DataProvider } from '../../types';
import { useIsOffline } from '../../core/useIsOffline';

export default {
title: 'ra-core/controller/field/ReferenceFieldBase',
Expand Down Expand Up @@ -395,6 +396,77 @@ export const WithRenderProp = ({ dataProvider = dataProviderWithAuthors }) => (
</TestMemoryRouter>
);

export const Offline = () => {
return (
<TestMemoryRouter initialEntries={['/books/1/show']}>
<CoreAdmin
dataProvider={dataProviderWithAuthors}
queryClient={
new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
})
}
>
<Resource name="authors" />
<Resource
name="books"
show={
<ShowBase>
<div>
<RenderChildOnDemand>
<ReferenceFieldBase
source="author"
reference="authors"
offline={
<p>
You are offline, cannot load
data
</p>
}
>
<MyReferenceField>
<TextField source="first_name" />
</MyReferenceField>
</ReferenceFieldBase>
</RenderChildOnDemand>
</div>
<SimulateOfflineButton />
</ShowBase>
}
/>
</CoreAdmin>
</TestMemoryRouter>
);
};

const SimulateOfflineButton = () => {
const isOffline = useIsOffline();
return (
<button
type="button"
onClick={() => onlineManager.setOnline(isOffline)}
>
{isOffline ? 'Simulate online' : 'Simulate offline'}
</button>
);
};

const RenderChildOnDemand = ({ children }) => {
const [showChild, setShowChild] = React.useState(false);
return (
<>
<button onClick={() => setShowChild(!showChild)}>
Toggle Child
</button>
{showChild && <div>{children}</div>}
</>
);
};

const MyReferenceField = (props: { children: React.ReactNode }) => {
const context = useReferenceFieldContext();

Expand Down
Loading
Loading