Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
41 changes: 40 additions & 1 deletion docs/Show.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ That's enough to display the post show view above.
| `disable Authentication` | Optional | `boolean` | | Set to `true` to disable the authentication check
| `empty WhileLoading` | Optional | `boolean` | | Set to `true` to return `null` while the show is loading
| `id` | Optional | `string | number` | | The record id. If not provided, it will be deduced from the URL
| `offline` | Optional | `ReactNode` | | The component to render when there is no connectivity and the record isn't in the cache
| `queryOptions` | Optional | `object` | | The options to pass to the `useQuery` hook
| `resource` | Optional | `string` | | The resource name, e.g. `posts`
| `sx` | Optional | `object` | | Override or extend the styles applied to the component
Expand All @@ -83,7 +84,7 @@ By default, `<Show>` includes an action toolbar with an `<EditButton>` if the `<

```jsx
import Button from '@mui/material/Button';
import { EditButton, TopToolbar } from 'react-admin';
import { EditButton, Show, TopToolbar } from 'react-admin';

const PostShowActions = () => (
<TopToolbar>
Expand Down Expand Up @@ -158,6 +159,8 @@ React-admin provides 2 built-in show layout components:
To use an alternative layout, switch the `<Show>` child component:

```diff
import { Show } from 'react-admin';

export const PostShow = () => (
<Show>
- <SimpleShowLayout>
Expand Down Expand Up @@ -188,6 +191,7 @@ You can override the main area container by passing a `component` prop:

{% raw %}
```jsx
import { Show } from 'react-admin';
import { Box } from '@mui/material';

const ShowWrapper = ({ children }) => (
Expand All @@ -210,6 +214,8 @@ const PostShow = () => (
By default, the `<Show>` component will automatically redirect the user to the login page if the user is not authenticated. If you want to disable this behavior and allow anonymous access to a show page, set the `disableAuthentication` prop to `true`.

```jsx
import { Show } from 'react-admin';

const PostShow = () => (
<Show disableAuthentication>
...
Expand Down Expand Up @@ -273,6 +279,8 @@ const BookShow = () => (
By default, `<Show>` deduces the identifier of the record to show from the URL path. So under the `/posts/123/show` path, the `id` prop will be `123`. You may want to force a different identifier. In this case, pass a custom `id` prop.

```jsx
import { Show } from 'react-admin';

export const PostShow = () => (
<Show id="123">
...
Expand All @@ -282,6 +290,35 @@ export const PostShow = () => (

**Tip**: Pass both a custom `id` and a custom `resource` prop to use `<Show>` independently of the current URL. This even allows you to use more than one `<Show>` component in the same page.

## `offline`

By default, `<Show>` renders the `<Offline>` component when there is no connectivity and the record hasn't been cached yet. You can provide your own component via the `offline` prop:

```jsx
import { Show } from 'react-admin';

export const PostShow = () => (
<Show offline={<p>No network. Could not load the post.</p>}>
...
</Show>
);
```

**Tip**: If the record is in the Tanstack Query cache but you want to warn the user that they may see an outdated version, you can use the `<IsOffline>` component:

```jsx
import { Show, IsOffline } from 'react-admin';

export const PostShow = () => (
<Show offline={<p>No network. Could not load the post.</p>}>
<IsOffline>
No network. The post data may be outdated.
</IsOffline>
...
</Show>
);
```

## `queryOptions`

`<Show>` accepts a `queryOptions` prop to pass options to the react-query client.
Expand Down Expand Up @@ -372,6 +409,8 @@ export const PostShow = () => (
By default, `<Show>` operates on the current `ResourceContext` (defined at the routing level), so under the `/posts/1/show` path, the `resource` prop will be `posts`. You may want to force a different resource. In this case, pass a custom `resource` prop, and it will override the `ResourceContext` value.

```jsx
import { Show } from 'react-admin';

export const UsersShow = () => (
<Show resource="users">
...
Expand Down
108 changes: 79 additions & 29 deletions docs/ShowBase.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ const App = () => (
| Prop | Required | Type | Default | Description
|------------------|----------|-------------------|---------|--------------------------------------------------------
| `children` | Optional | `ReactNode` | | The components rendering the record fields
| `render` | Optional | `(props: ShowControllerResult<RecordType>) => ReactNode` | | Alternative to children, a function that takes the ShowController context and renders the form
| `render` | Optional | `(props: ShowControllerResult<RecordType>) => ReactNode` | | Alternative to children, a function that takes the ShowController context and renders the form
| `disable Authentication` | Optional | `boolean` | | Set to `true` to disable the authentication check
| `empty WhileLoading` | Optional | `boolean` | | Set to `true` to return `null` while the list is loading
| `id` | Optional | `string` | | The record identifier. If not provided, it will be deduced from the URL
| `loading` | Optional | `ReactNode` | | The component to render while checking for authentication and permissions
| `offline` | Optional | `ReactNode` | | The component to render when there is no connectivity and the record isn't in the cache
| `queryOptions` | Optional | `object` | | The options to pass to the `useQuery` hook
| `resource` | Optional | `string` | | The resource name, e.g. `posts`

Expand Down Expand Up @@ -107,38 +108,13 @@ const BookShow = () => (
```
{% endraw %}

## `render`

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

{% raw %}
```jsx
import { ShowBase, TextField, DateField, ReferenceField, WithRecord } from 'react-admin';

const BookShow = () => (
<ShowBase render={({ isPending, error, record }) => {
if (isPending) {
return <p>Loading...</p>;
}

if (error) {
return (
<p className="error">
{error.message}
</p>
);
}
return <p>{record.title}</p>;
}}/>
);
```
{% endraw %}

## `disableAuthentication`

By default, the `<ShowBase>` component will automatically redirect the user to the login page if the user is not authenticated. If you want to disable this behavior and allow anonymous access to a show page, set the `disableAuthentication` prop to `true`.

```jsx
import { ShowBase } from 'ra-core';

const PostShow = () => (
<ShowBase disableAuthentication>
...
Expand All @@ -151,6 +127,8 @@ const PostShow = () => (
By default, `<ShowBase>` deduces the identifier of the record to show from the URL path. So under the `/posts/123/show` path, the `id` prop will be `123`. You may want to force a different identifier. In this case, pass a custom `id` prop.

```jsx
import { ShowBase } from 'ra-core';

export const PostShow = () => (
<ShowBase id="123">
...
Expand All @@ -160,6 +138,49 @@ export const PostShow = () => (

**Tip**: Pass both a custom `id` and a custom `resource` prop to use `<ShowBase>` independently of the current URL. This even allows you to use more than one `<ShowBase>` component in the same page.

## `loading`

By default, `<ShowBase>` renders nothing while checking for authentication and permissions. You can provide your own component via the `loading` prop:

```jsx
import { ShowBase } from 'ra-core';

export const PostShow = () => (
<ShowBase loading={<p>Checking for permissions...</p>}>
...
</ShowBase>
);
```

## `offline`

By default, `<ShowBase>` renders nothing when there is no connectivity and the record hasn't been cached yet. You can provide your own component via the `offline` prop:

```jsx
import { ShowBase } from 'ra-core';

export const PostShow = () => (
<ShowBase offline={<p>No network. Could not load the post.</p>}>
...
</ShowBase>
);
```

**Tip**: If the record is in the Tanstack Query cache but you want to warn the user that they may see an outdated version, you can use the `<IsOffline>` component:

```jsx
import { ShowBase, IsOffline } from 'ra-core';

export const PostShow = () => (
<ShowBase offline={<p>No network. Could not load the post.</p>}>
<IsOffline>
No network. The post data may be outdated.
</IsOffline>
...
</ShowBase>
);
```

## `queryOptions`

`<ShowBase>` accepts a `queryOptions` prop to pass options to the react-query client.
Expand Down Expand Up @@ -205,11 +226,40 @@ The default `onError` function is:
}
```

## `render`

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

{% raw %}
```jsx
import { ShowBase, TextField, DateField, ReferenceField, WithRecord } from 'react-admin';

const BookShow = () => (
<ShowBase render={({ isPending, error, record }) => {
if (isPending) {
return <p>Loading...</p>;
}

if (error) {
return (
<p className="error">
{error.message}
</p>
);
}
return <p>{record.title}</p>;
}}/>
);
```
{% endraw %}

## `resource`

By default, `<ShowBase>` operates on the current `ResourceContext` (defined at the routing level), so under the `/posts/1/show` path, the `resource` prop will be `posts`. You may want to force a different resource. In this case, pass a custom `resource` prop, and it will override the `ResourceContext` value.

```jsx
import { ShowBase } from 'ra-core';

export const UsersShow = () => (
<ShowBase resource="users">
...
Expand Down
13 changes: 13 additions & 0 deletions packages/ra-core/src/controller/show/ShowBase.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
AccessControl,
DefaultTitle,
NoAuthProvider,
Offline,
WithAuthProviderNoAccessControl,
WithRenderProp,
} from './ShowBase.stories';
Expand Down Expand Up @@ -118,4 +119,16 @@ describe('ShowBase', () => {
expect(dataProvider.getOne).toHaveBeenCalled();
await screen.findByText('Hello');
});

it('should render the offline prop node when offline', async () => {
const { rerender } = render(<Offline isOnline={false} />);
await screen.findByText('You are offline, cannot load data');
rerender(<Offline isOnline={true} />);
await screen.findByText('Hello');
expect(
screen.queryByText('You are offline, cannot load data')
).toBeNull();
rerender(<Offline isOnline={false} />);
await screen.findByText('You are offline, the data may be outdated');
});
});
71 changes: 62 additions & 9 deletions packages/ra-core/src/controller/show/ShowBase.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@ import * as React from 'react';
import englishMessages from 'ra-language-english';
import frenchMessages from 'ra-language-french';
import polyglotI18nProvider from 'ra-i18n-polyglot';
import fakeRestDataProvider from 'ra-data-fakerest';
import {
AuthProvider,
CoreAdminContext,
ShowBase,
ShowBaseProps,
DataProvider,
testDataProvider,
useRecordContext,
mergeTranslations,
I18nProvider,
useShowContext,
useLocaleState,
IsOffline,
WithRecord,
} from '../..';
import { onlineManager } from '@tanstack/react-query';

export default {
title: 'ra-core/controller/ShowBase',
Expand Down Expand Up @@ -162,21 +164,72 @@ export const WithRenderProp = ({
</CoreAdminContext>
);

const defaultDataProvider = testDataProvider({
getOne: () =>
// @ts-ignore
Promise.resolve({ data: { id: 12, test: 'Hello', title: 'Hello' } }),
});
export const Offline = ({
dataProvider = defaultDataProvider,
isOnline = true,
...props
}: {
dataProvider?: DataProvider;
isOnline?: boolean;
} & Partial<ShowBaseProps>) => {
React.useEffect(() => {
onlineManager.setOnline(isOnline);
}, [isOnline]);
return (
<CoreAdminContext dataProvider={dataProvider}>
<ShowBase
{...defaultProps}
{...props}
offline={<p>You are offline, cannot load data</p>}
>
<OfflineChild />
</ShowBase>
</CoreAdminContext>
);
};

Offline.args = {
isOnline: true,
};

Offline.argTypes = {
isOnline: {
control: { type: 'boolean' },
},
};

const defaultDataProvider = fakeRestDataProvider(
{
posts: [
{ id: 12, test: 'Hello', title: 'Hello' },
{ id: 13, test: 'World', title: 'World' },
],
},
process.env.NODE_ENV !== 'test',
process.env.NODE_ENV !== 'test' ? 300 : 0
);

const defaultProps = {
id: 12,
resource: 'posts',
};

const Child = () => {
const record = useRecordContext();
return <WithRecord render={record => <p>{record?.test}</p>} />;
};

return <p>{record?.test}</p>;
const OfflineChild = () => {
return (
<>
<p>Use the story controls to simulate offline mode:</p>
<IsOffline>
<p style={{ color: 'orange' }}>
You are offline, the data may be outdated
</p>
</IsOffline>
<WithRecord render={record => <p>{record?.test}</p>} />
</>
);
};

const Title = () => {
Expand Down
Loading
Loading