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
24 changes: 24 additions & 0 deletions docs/ReferenceOneField.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const BookShow = () => (
| `empty` | Optional | `ReactNode` | - | The text or element to display when the referenced record is empty |
| `filter` | Optional | `Object` | `{}` | Used to filter referenced records |
| `link` | Optional | `string | Function` | `edit` | Target of the link wrapping the rendered child. Set to `false` to disable the link. |
| `offline` | Optional | `ReactNode` | - | The text or element to display when there is no network connectivity |
| `queryOptions` | Optional | [`UseQueryOptions`](https://tanstack.com/query/v5/docs/react/reference/useQuery) | `{}` | `react-query` client options |
| `sort` | Optional | `{ field: String, order: 'ASC' or 'DESC' }` | `{ field: 'id', order: 'ASC' }` | Used to order referenced records |

Expand Down Expand Up @@ -156,6 +157,29 @@ You can also set the `link` prop to a string, which will be used as the link typ
</ReferenceOneField>
```


## `offline`

When the user is offline, `<ReferenceOneField>` is smart enough to display the referenced record if it was previously fetched. However, if the referenced record has never been fetched before, `<ReferenceOneField>` 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
<ReferenceOneField
reference="book_details"
target="book_id"
offline={<p>No network, could not fetch data</p>}
>
...
</ReferenceOneField>
<ReferenceOneField
reference="book_details"
target="book_id"
offline="No network, could not fetch data">
...
</ReferenceOneField>
```

## `queryOptions`

`<ReferenceOneField>` uses `react-query` to fetch the related record. You can set [any of `useQuery` options](https://tanstack.com/query/v5/docs/react/reference/useQuery) via the `queryOptions` prop.
Expand Down
89 changes: 56 additions & 33 deletions docs/ReferenceOneFieldBase.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const BookDetails = () => {
| `empty` | Optional | `ReactNode` | - | The text or element to display when the referenced record is empty |
| `filter` | Optional | `Object` | `{}` | Used to filter referenced records |
| `link` | Optional | `string | Function` | `edit` | Target of the link wrapping the rendered child. Set to `false` to disable the link. |
| `offline` | Optional | `ReactNode` | - | The text or element to display when there is no network connectivity |
| `queryOptions` | Optional | [`UseQueryOptions`](https://tanstack.com/query/v5/docs/react/reference/useQuery) | `{}` | `react-query` client options |
| `sort` | Optional | `{ field: String, order: 'ASC' or 'DESC' }` | `{ field: 'id', order: 'ASC' }` | Used to order referenced records |

Expand Down Expand Up @@ -122,39 +123,6 @@ const BookDetails = () => {
}
```

## `render`

Alternatively to children you can pass a `render` function prop to `<ReferenceOneFieldBase>`. The `render` function prop will receive the `ReferenceFieldContext` as its argument, allowing to inline the render logic.
When receiving a `render` function prop the `<ReferenceOneFieldBase>` component will ignore the children property.

```jsx
const BookShow = () => (
<ReferenceOneFieldBase
reference="book_details"
target="book_id"
render={({ isPending, error, referenceRecord }) => {
if (isPending) {
return <p>Loading...</p>;
}

if (error) {
return <p className="error" >{error.toString()}</p>;
}

if (!referenceRecord) {
return <p>No details found</p>;
}
return (
<div>
<p>{referenceRecord.genre}</p>
<p>{referenceRecord.ISBN}</p>
</div>
);
}}
/>
);
```

## `empty`

Use `empty` to customize the text displayed when the related record is empty.
Expand Down Expand Up @@ -228,6 +196,28 @@ You can also set the `link` prop to a string, which will be used as the link typ
```
{% endraw %}

## `offline`

When the user is offline, `<ReferenceOneFieldBase>` is smart enough to display the referenced record if it was previously fetched. However, if the referenced record has never been fetched before, `<ReferenceOneFieldBase>` 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
<ReferenceOneFieldBase
reference="book_details"
target="book_id"
offline={<p>No network, could not fetch data</p>}
>
...
</ReferenceOneFieldBase>
<ReferenceOneFieldBase
reference="book_details"
target="book_id"
offline="No network, could not fetch data">
...
</ReferenceOneFieldBase>
```

## `queryOptions`

`<ReferenceOneFieldBase>` uses `react-query` to fetch the related record. You can set [any of `useQuery` options](https://tanstack.com/query/v5/docs/react/reference/useQuery) via the `queryOptions` prop.
Expand All @@ -247,6 +237,39 @@ For instance, if you want to disable the refetch on window focus for this query,
```
{% endraw %}

## `render`

Alternatively to children you can pass a `render` function prop to `<ReferenceOneFieldBase>`. The `render` function prop will receive the `ReferenceFieldContext` as its argument, allowing to inline the render logic.
When receiving a `render` function prop the `<ReferenceOneFieldBase>` component will ignore the children property.

```jsx
const BookShow = () => (
<ReferenceOneFieldBase
reference="book_details"
target="book_id"
render={({ isPending, error, referenceRecord }) => {
if (isPending) {
return <p>Loading...</p>;
}

if (error) {
return <p className="error" >{error.toString()}</p>;
}

if (!referenceRecord) {
return <p>No details found</p>;
}
return (
<div>
<p>{referenceRecord.genre}</p>
<p>{referenceRecord.ISBN}</p>
</div>
);
}}
/>
);
```

## `reference`

The name of the resource to fetch for the related records.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import {
Basic,
Loading,
Offline,
WithRenderProp,
} from './ReferenceOneFieldBase.stories';

Expand Down Expand Up @@ -61,4 +62,13 @@ describe('ReferenceOneFieldBase', () => {
});
});
});

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('Offline');
fireEvent.click(await screen.findByText('Simulate online'));
await screen.findByText('9780393966473');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import {
ReferenceOneFieldBase,
ResourceContextProvider,
TestMemoryRouter,
useIsOffline,
useReferenceFieldContext,
} from '../..';
import { onlineManager } from '@tanstack/react-query';

export default { title: 'ra-core/controller/field/ReferenceOneFieldBase' };

Expand Down Expand Up @@ -100,3 +102,46 @@ export const WithRenderProp = ({
</Wrapper>
);
};

export const Offline = () => {
return (
<Wrapper>
<div>
<RenderChildOnDemand>
<ReferenceOneFieldBase
reference="book_details"
target="book_id"
offline={<span>Offline</span>}
>
<BookDetails />
</ReferenceOneFieldBase>
</RenderChildOnDemand>
</div>
<SimulateOfflineButton />
</Wrapper>
);
};

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>}
</>
);
};
66 changes: 38 additions & 28 deletions packages/ra-core/src/controller/field/ReferenceOneFieldBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const ReferenceOneFieldBase = <
sort,
filter,
link,
offline,
queryOptions,
} = props;

Expand Down Expand Up @@ -78,38 +79,46 @@ export const ReferenceOneFieldBase = <
}

const recordFromContext = useRecordContext<RecordType>(props);
if (controllerProps.isPending && loading) {
return (
<ResourceContextProvider value={reference}>
{loading}
</ResourceContextProvider>
);
}
if (controllerProps.error && error) {
return (
<ResourceContextProvider value={reference}>
<ReferenceFieldContextProvider value={context}>
{error}
</ReferenceFieldContextProvider>
</ResourceContextProvider>
);
}
if (
!recordFromContext ||
(!controllerProps.isPending && controllerProps.referenceRecord == null)
) {
return (
<ResourceContextProvider value={reference}>
{empty}
</ResourceContextProvider>
);
}
const {
error: controllerError,
isPending,
isPaused,
referenceRecord,
} = controllerProps;

const shouldRenderLoading =
!isPaused && isPending && loading !== false && loading !== undefined;
const shouldRenderOffline =
isPaused &&
!referenceRecord &&
offline !== false &&
offline !== undefined;
const shouldRenderError =
!!controllerError && error !== false && error !== undefined;
const shouldRenderEmpty =
(!recordFromContext ||
(!isPaused &&
referenceRecord == null &&
!controllerError &&
!isPending)) &&
empty !== false &&
empty !== undefined;

return (
<ResourceContextProvider value={reference}>
<ReferenceFieldContextProvider value={context}>
<RecordContextProvider value={context.referenceRecord}>
{render ? render(controllerProps) : children}
<RecordContextProvider value={referenceRecord}>
{shouldRenderLoading
? loading
: shouldRenderOffline
? offline
: shouldRenderError
? error
: shouldRenderEmpty
? empty
: render
? render(controllerProps)
: children}
</RecordContextProvider>
</ReferenceFieldContextProvider>
</ResourceContextProvider>
Expand All @@ -127,6 +136,7 @@ export interface ReferenceOneFieldBaseProps<
loading?: ReactNode;
error?: ReactNode;
empty?: ReactNode;
offline?: ReactNode;
render?: (props: UseReferenceResult<ReferenceRecordType>) => ReactNode;
link?: LinkToType<ReferenceRecordType>;
resource?: string;
Expand Down
10 changes: 10 additions & 0 deletions packages/ra-ui-materialui/src/field/ReferenceOneField.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Empty,
Themed,
WithRenderProp,
Offline,
} from './ReferenceOneField.stories';

describe('ReferenceOneField', () => {
Expand Down Expand Up @@ -86,4 +87,13 @@ describe('ReferenceOneField', () => {
render(<Themed />);
expect(await screen.findByTestId('themed')).toBeDefined();
});

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('No connectivity. Could not fetch data.');
fireEvent.click(await screen.findByText('Simulate online'));
await screen.findByText('9780393966473');
});
});
Loading
Loading