<Show>

The <Show> component is a page component that renders a single record.

post show view

<Show> handles the logic of the Show page:

  • it calls useShowController to fetch the record from the dataProvider via dataProvider.getOne(),
  • it computes the default page title
  • it creates a ShowContext and a RecordContext,
  • it renders the page layout with the correct title and actions
  • it renders its child component (a show layout component like <SimpleShowLayout> or <TabbedShowLayout>) in a Material UI <Card>

Usage

Here is the minimal code necessary to display a view to show a post:

// in src/posts.jsx
import { Show, SimpleShowLayout, TextField, DateField, RichTextField } from 'react-admin';

export const PostShow = () => (
    <Show>
        <SimpleShowLayout>
            <TextField source="title" />
            <TextField source="teaser" />
            <RichTextField source="body" />
            <DateField label="Publication date" source="published_at" />
        </SimpleShowLayout>
    </Show>
);

Components using <Show> can be used as the show prop of a <Resource> component:

// in src/App.jsx
import { Admin, Resource } from 'react-admin';

import { dataProvider } from './dataProvider';
import { PostShow } from './posts';

const App = () => (
    <Admin dataProvider={dataProvider}>
        <Resource name="posts" show={PostShow} />
    </Admin>
);

That’s enough to display the post show view above.

Props

Prop Required Type Default Description
children Required ReactNode   The components rendering the record fields
actions Optional ReactElement   The actions to display in the toolbar
aside Optional ReactElement   The component to display on the side of the list
className Optional string   passed to the root component
component Optional Component Card The component to render as the root element
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 | number   The record id. If not provided, it will be deduced from the URL
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
title Optional string | ReactElement   The title to display in the App Bar

actions

By default, <Show> includes an action toolbar with an <EditButton> if the <Resource> declared an edit component. You can replace the list of default actions by your own component using the actions prop:

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

const PostShowActions = () => (
    <TopToolbar>
        <EditButton />
        {/* Add your custom actions */}
        <Button color="primary" onClick={customAction}>Custom Action</Button>
    </TopToolbar>
);

export const PostShow = () => (
    <Show actions={<PostShowActions />}>
        ...
    </Show>
);

aside

You can pass an aside element to the <Show> component. It will be rendered on the right side of the page, below the actions toolbar.

The aside component renders in the same RecordContext as the Show child component. That means you can display details of the current record in the aside component by calling useRecordContext:

import {
    Show,
    SimpleShowLayout,
    TextField,
    DateField,
    RichTextField,
    useRecordContext
} from 'react-admin';

export const PostShow = () => (
    <Show aside={<Aside />}>
        <SimpleShowLayout>
            <TextField source="title" />
            <TextField source="teaser" />
            <RichTextField source="body" />
            <DateField label="Publication date" source="published_at" />
        </SimpleShowLayout>
    </Show>
);

const Aside = () => {
    const record = useRecordContext();
    return (
        <div style={{ width: 200, margin: '1em' }}>
            <Typography variant="h6">Post details</Typography>
            {record && (
                <Typography variant="body2">
                    Creation date: {record.createdAt}
                </Typography>
            )}
        </div>
    );
};

Tip: Always test the record is defined before using it, as react-admin starts rendering the UI before the dataProvider.getOne() call is over.

children

<Show> doesn’t render any field by default - it delegates this to its children, called “Show layout components”. These components read the record from the RecordContext and render its fields.

React-admin provides 2 built-in show layout components:

To use an alternative layout, switch the <Show> child component:

export const PostShow = () => (
    <Show>
-       <SimpleShowLayout>
+       <TabbedShowLayout>
+           <TabbedShowLayout.Tab label="Main">
                <TextField source="title" />
                <TextField source="teaser" />
                <RichTextField source="body" />
                <DateField label="Publication date" source="created_at" />
+           </TabbedShowLayout.Tab>
-       </SimpleShowLayout>
+       </TabbedShowLayout>
    </Show>
);

You can also pass a React element as child, to build a custom layout. Check Building a custom Show Layout for more details.

Tip: Use <ShowGuesser> instead of <Show> to let react-admin guess the fields to display based on the dataProvider response.

component

By default, the Show view renders the main content area inside a Material UI <Card>. The actual layout of the record fields depends on the Show Layout component you’re using (<SimpleShowLayout>, <TabbedShowLayout>, or a custom layout component).

You can override the main area container by passing a component prop:

import { Box } from '@mui/material';

const ShowWrapper = ({ children }) => (
    <Box sx={{ margin: 2, border: 'solid 1px grey' }}>
        {children}
    </Box>
);

// use a ShowWrapper as root component
const PostShow = () => (
    <Show component={ShowWrapper}>
        ...
    </Show>
);

disableAuthentication

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.

const PostShow = () => (
    <Show disableAuthentication>
        ...
    </Show>
);

emptyWhileLoading

By default, <Show> renders its child component even before the dataProvider.getOne() call returns. If you use <SimpleShowLayout> or <TabbedShowLayout>, this isn’t a problem as these components only render when the record has been fetched.

But if you use a custom child component that expects the record context to be defined, your component will throw an error. For instance, the following will fail on load with a “ReferenceError: data is not defined” error:

import { Show, useShowContext } from 'react-admin';
import { Stack, Typography } from '@mui/icons-material/Star';

const SimpleBookShow = () => {
    const { record } = useShowContext();
    return (
        <Typography>
            <i>{record.title}</i>, by {record.author} ({record.year})
        </Typography>
    );
}

const BookShow = () => (
    <Show>
        <SimpleBookShow />
    </Show>
);

You can handle this case by getting the isLoading variable from the useShowContext hook:

const SimpleBookShow = () => {
    const { record, isLoading } = useShowContext();
    if (isLoading) return null;
    return (
        <Typography>
            <i>{record.title}</i>, by {record.author} ({record.year})
        </Typography>
    );
}

The <Show emptyWhileLoading> prop provides a convenient shortcut for that use case. When enabled, <Show> won’t render its child until data is defined.

const BookShow = () => (
-   <Show>
+   <Show emptyWhileLoading>
        <SimpleBookShow />
    </Show>
);

id

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.

export const PostShow = () => (
    <Show id="123">
        ...
    </Show>
);

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.

queryOptions

<Show> accepts a queryOptions prop to pass options to the react-query client.

This can be useful e.g. to pass a custom meta to the dataProvider.getOne() call.

import { Show } from 'react-admin';

export const PostShow = () => (
    <Show queryOptions={{ meta: { foo: 'bar' }}}>
        ...
    </Show>
);

With this option, react-admin will call dataProvider.getOne() on mount with the ` meta: { foo: ‘bar’ }` option.

You can also use the queryOptions prop to override the default error side effect. By default, when the dataProvider.getOne() call fails at the dataProvider level, react-admin shows an error notification and refreshes the page.

You can override this behavior and pass custom side effects by providing a custom queryOptions prop:

import * as React from 'react';
import { useNotify, useRefresh, useRedirect, Show, SimpleShowLayout } from 'react-admin';

const PostShow = () => {
    const notify = useNotify();
    const refresh = useRefresh();
    const redirect = useRedirect();

    const onError = (error) => {
        notify(`Could not load post: ${error.message}`, { type: 'error' });
        redirect('/posts');
        refresh();
    };

    return (
        <Show queryOptions={{ onError }}>
            <SimpleShowLayout>
                ...
            </SimpleShowLayout>
        </Show>
    );
}

The onError function receives the error from the dataProvider call (dataProvider.getOne()), which is a JavaScript Error object (see the dataProvider documentation for details).

The default onError function is:

(error) => {
    notify('ra.notification.item_doesnt_exist', { type: 'error' });
    redirect('list', resource);
    refresh();
}

resource

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.

export const UsersShow = () => (
    <Show resource="users">
        ...
    </Show>
);

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.

sx: CSS API

The <Show> component accepts the usual className prop, but you can override many class names injected to the inner components by React-admin thanks to the sx property (see the sx documentation for syntax and examples). This property accepts the following subclasses:

Rule name Description
& .RaShow-main Applied to the main container
& .RaShow-card Applied to the <Card> element

Here’s an example of how to override the default styles:

const PostShow = () => (
    <Show 
        sx={{
            backgroundColor: 'yellow',
            '& .RaShow-main': {
                backgroundColor: 'red',
            },
        }}
    >
            ...
    </Show>
);

To override the style of all instances of <Show> using the application-wide style overrides, use the RaShow key.

title

By default, the title for the Show view is "[resource_name] #[record_id]".

You can customize this title by specifying a custom title prop:

export const PostShow = () => (
    <Show title="Post view">
        ...
    </Show>
);

More interestingly, you can pass a component as title. React-admin clones this component, which can access the current record via useRecordContext. This allows to customize the title according to the current record:

import { useRecordContext, Show } from 'react-admin';

const PostTitle = () => {
    const record = useRecordContext();
    // the record can be empty while loading
    if (!record) return null;
    return <span>Post "{record.title}"</span>;
};

export const PostShow = () => (
    <Show title={<PostTitle />}>
        ...
    </Show>
);

Loading State

Default layout components (<SimpleShowLayout> and <TabbedshowLayout>) return null when the record is loading. If you use a custom layout component instead, you’ll have to handle the case where the record is not yet defined.

That means that the following will fail on load with a “ReferenceError: record is not defined” error:

import { Show, useRecordContext } from 'react-admin';
import { Card } from '@mui/material';

const PostTitle = () => {
    const record = useRecordContext();
    return <span>{record.title}</span>;
};

const PostShow = () => (
    <Show>
        <Card>
            <div>Title: <PostTitle /></div>
        </Card>
    </Show>
);

You can handle this case by calling the useShowContext hook to get the loading state:

import { useShowContext, useRecordContext } from 'react-admin';

const PostTitle = () => {
    const record = useRecordContext();
    const { isLoading } = useShowContext();
    if (!isLoading) return null;
    return <span>{record.title}</span>;
};

But this can be cumbersome, as you need to do it in every field component.

The <Show emptyWhileLoading> prop provides a convenient shortcut for that use case. When enabled, <Show> won’t render its child until record is defined.

const PostTitle = () => {
    const record = useRecordContext();
    return <span>{record.title}</span>;
};

const PostShow = () => (
-   <Show>
+   <Show emptyWhileLoading>
        <Card>
            <div>Title: <PostTitle /></div>
        </Card>
    </Show>
);

Displaying Fields Depending On User Permissions

If you want to display some fields only to users with specific permissions, use the usePermissions hook and JSX conditions to show or hide fields.

Here’s an example inside a Show view with a SimpleShowLayout and a custom actions component:

import TopToolbar from '@mui/material/TopToolbar';
import Button from '@mui/material/Button';
import { Show, SimpleShowLayout, RichTextField, NumberField, usePermissions, EditButton, DeleteButton } from 'react-admin';

const PostShowActions = () => {
    const { permissions } = usePermissions(); 
    return (
        <TopToolbar>
            <EditButton />
            {permissions === 'admin' && <DeleteButton />}
        </TopToolbar>
    );
}

export const PostShow = () => {
    const { permissions } = usePermissions();
    return (
        <Show actions={<PostShowActions />}>
            <SimpleShowLayout>
                <TextField source="title" />
                <RichTextField source="body" />
                {permissions === 'admin' &&
                    <NumberField source="nb_views" />
                }
            </SimpleShowLayout>
        </Show>
    );
}

This also works inside a <TabbedShowLayout>, and you can hide a TabbedShowLayout.Tab completely:

import { Show, TabbedShowLayout, TextField } from 'react-admin';

export const UserShow = () => {
    const { permissions } = usePermissions();
    return (
        <Show>
            <TabbedShowLayout>
                <TabbedShowLayout.Tab label="user.form.summary">
                    {permissions === 'admin' && <TextField source="id" />}
                    <TextField source="name" />
                </TabbedShowLayout.Tab>
                {permissions === 'admin' &&
                    <TabbedShowLayout.Tab label="user.form.security">
                        <TextField source="role" />
                    </TabbedShowLayout.Tab>}
            </TabbedShowLayout>
        </Show>
    );
}

For more details about permissions, check out the authProvider documentation.

Adding meta To The DataProvider Call

Use the queryOptions prop to pass a custom meta to the dataProvider.getOne() call.

import { Show } from 'react-admin';

export const PostShow = () => (
    <Show queryOptions={{ meta: { foo: 'bar' }}}>
        ...
    </Show>
);

Live Updates

If you want to subscribe to live updates on the record (topic: resource/[resource]/[id]), use the <ShowLive> component instead.

-import { Show, SimpleShowLayout, TextField } from 'react-admin';
+import { SimpleShowLayout, TextField } from 'react-admin';
+import { ShowLive } from '@react-admin/ra-realtime';

const PostShow = () => (
-   <Show>
+   <ShowLive>
        <SimpleShowLayout>
            <TextField source="title" />
        </SimpleShowLayout>
-   </Show>
+   </ShowLive>
);

It shows a notification and refreshes the page when the record is updated by another user. Also, it displays a warning when the record is deleted by another user.

The <PrevNextButtons renders a navigation with two buttons, allowing users to navigate through records without leaving a <Show> view.

The following code is an example of how you can use it:

export const PostShow = () => (
    <Show
        actions={
            <TopToolbar>
                <PrevNextButtons linkType="show"/>
            </TopToolbar>
        }
    >
    ...
    </Show>
);

Tips: If you want the <PrevNextButtons> to link to the <Show> view, you have to set the linkType to show. See the <PrevNextButtons linkType> prop.

Controlled Mode

<show> deduces the resource and the record id from the URL. This is fine for a detail page, but if you need to embed the details of a record in another page, you probably want to define these parameters yourself.

In that case, use the resource and id props to set the show parameters regardless of the URL.

import { Show, SelectField, SimpleShowLayout, TextField } from "react-admin";

export const BookShow = ({ id }) => (
    <Show resource="books" id={id}>
        <SimpleShowLayout>
            <TextField source="title" />
            <TextField source="author" />
            <SelectField source="availability" choices={[
                { id: "in_stock", name: "In stock" },
                { id: "out_of_stock", name: "Out of stock" },
                { id: "out_of_print", name: "Out of print" },
            ]} />
        </SimpleShowLayout>
    </Show>
);

Headless Version

Besides fetching a record, <Show> renders the default detail page layout (title, actions, a Material UI <Card>) and its children. If you need a custom detail layout, you may prefer the <ShowBase> component, which only renders its children in a ShowContext.

import { ShowBase, SelectField, SimpleShowLayout, TextField, Title } from "react-admin";
import { Card, CardContent, Container } from "@mui/material";

export const BookShow = () => (
    <ShowBase>
        <Container>
            <Title title="Book Detail" />
            <Card>
                <CardContent>
                    <SimpleShowLayout>
                        <TextField source="title" />
                        <TextField source="author" />
                        <SelectField source="availability" choices={[
                            { id: "in_stock", name: "In stock" },
                            { id: "out_of_stock", name: "Out of stock" },
                            { id: "out_of_print", name: "Out of print" },
                        ]} />
                    </SimpleShowLayout>
                </CardContent>
            </Card>
        </Container>
    </ShowBase>
);

In the previous example, <SimpleShowLayout> grabs the record from the ShowContext.

If you don’t need the ShowContext, you can use the useShowController hook, which does the same data fetching as <ShowBase> but lets you render the content.

import { useShowController, SelectField, SimpleShowLayout, TextField, Title } from "react-admin";
import { Card, CardContent, Container } from "@mui/material";

export const BookShow = () => {
    const { record } = useShowController();
    return (
        <Container>
            <Title title={`Edit book ${record?.title}`} />
            <Card>
                <CardContent>
                    <SimpleShowLayout record={record}>
                        <TextField source="title" />
                        <TextField source="author" />
                        <SelectField source="availability" choices={[
                            { id: "in_stock", name: "In stock" },
                            { id: "out_of_stock", name: "Out of stock" },
                            { id: "out_of_print", name: "Out of print" },
                        ]} />
                    </SimpleShowLayout>
                </CardContent>
            </Card>
        </Container>
    );
};