Skip to content

Commit

Permalink
Merge pull request #10450 from marmelab/feat/support_ReferenceOneInpu…
Browse files Browse the repository at this point in the history
…t_emptyContent

Support `<ReferenceOneField emptyContent>`
  • Loading branch information
djhi authored Jan 24, 2025
2 parents 1c9a9dd + 326a65c commit 55c7e58
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 25 deletions.
13 changes: 13 additions & 0 deletions docs/ReferenceOneField.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,19 @@ Use `emptyText` to customize the text displayed when the related record is empty
</ReferenceOneField>
```

`emptyText` also accepts a `ReactElement`.

```jsx
<ReferenceOneField
label="Details"
reference="book_details"
target="book_id"
emptyText={<CreateButton to="/book_details/create" />}
>
<TextField source="genre" /> (<TextField source="ISBN" />)
</ReferenceOneField>
```

## `filter`

You can also use `<ReferenceOneField>` in a one-to-many relationship. In that case, the first record will be displayed. The `filter` prop becomes super useful in that case, as it allows you to select the appropriate record to display.
Expand Down
14 changes: 13 additions & 1 deletion packages/ra-ui-materialui/src/field/ReferenceOneField.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import * as React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';

import {
RecordRepresentation,
Basic,
EmptyWithTranslate,
QueryOptions,
EmptyText,
} from './ReferenceOneField.stories';

describe('ReferenceOneField', () => {
it('should render the recordRepresentation of the related record', async () => {
render(<RecordRepresentation />);
await screen.findByText('Genre: novel, ISBN: 9780393966473');
});

it('should render its child in the context of the related record', async () => {
render(<Basic />);
await screen.findByText('9780393966473');
Expand All @@ -23,6 +25,7 @@ describe('ReferenceOneField', () => {

await screen.findByText('Not found');
});

it('should accept a queryOptions prop', async () => {
const dataProvider = {
getManyReference: jest.fn().mockImplementationOnce(() =>
Expand All @@ -48,4 +51,13 @@ describe('ReferenceOneField', () => {
);
});
});

it('should render the "emptyContent" prop when the record is not found', async () => {
render(<EmptyText />);
await waitFor(() => {
expect(screen.queryAllByText('no detail')).toHaveLength(3);
});
fireEvent.click(screen.getByText('War and Peace'));
await screen.findByText('Create');
});
});
136 changes: 119 additions & 17 deletions packages/ra-ui-materialui/src/field/ReferenceOneField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,30 @@ import {
useRecordContext,
I18nContextProvider,
TestMemoryRouter,
Resource,
} from 'ra-core';

import { ThemeProvider, Stack } from '@mui/material';
import { createTheme } from '@mui/material/styles';
import fakeRestDataProvider from 'ra-data-fakerest';
import polyglotI18nProvider from 'ra-i18n-polyglot';
import englishMessages from 'ra-language-english';
import { ThemeProvider, Stack } from '@mui/material';
import { createTheme } from '@mui/material/styles';

import { TextField } from '../field';
import { ReferenceOneField } from './ReferenceOneField';
import { SimpleShowLayout } from '../detail/SimpleShowLayout';
import { Datagrid } from '../list/datagrid/Datagrid';
import {
ReferenceOneField,
ReferenceField,
ReferenceInput,
AdminContext,
AdminUI,
CreateButton,
Create,
List,
Show,
SimpleShowLayout,
SimpleForm,
Datagrid,
TextField,
TextInput,
} from '..';

export default { title: 'ra-ui-materialui/fields/ReferenceOneField' };

Expand Down Expand Up @@ -103,16 +116,105 @@ const emptyDataProvider = {
}),
} as any;

export const Empty = () => (
<Wrapper dataProvider={emptyDataProvider}>
<ReferenceOneField
reference="book_details"
target="book_id"
emptyText="no detail"
>
<TextField source="ISBN" />
</ReferenceOneField>
</Wrapper>
const dataProvider = fakeRestDataProvider({
book_details: [],
books: [
{
id: 1,
title: 'War and Peace',
year: 1869,
Genre: 'Historical',
},
{
id: 2,
title: 'Anna Karenina',
year: 1877,
Genre: 'Romance',
},
{
id: 3,
title: 'The Death of Ivan Ilyich',
year: 1886,
Genre: 'Philosophical',
},
],
});

export const EmptyText = () => (
<TestMemoryRouter>
<AdminContext dataProvider={dataProvider} i18nProvider={i18nProvider}>
<AdminUI>
<Resource
name="books"
list={() => (
<List>
<Datagrid>
<TextField source="id" />
<TextField source="title" />
<TextField source="year" />
<TextField source="Genre" />
<ReferenceOneField
reference="book_details"
target="book_id"
label="ISBN"
emptyText="no detail"
>
<TextField source="ISBN" />
</ReferenceOneField>
</Datagrid>
</List>
)}
show={() => (
<Show>
<SimpleShowLayout>
<TextField source="id" />
<TextField source="title" />
<TextField source="year" />
<TextField source="Genre" />
<ReferenceOneField
reference="book_details"
target="book_id"
label="ISBN"
emptyText={
<CreateButton to="/book_details/create" />
}
>
<TextField source="ISBN" />
</ReferenceOneField>
</SimpleShowLayout>
</Show>
)}
/>
<Resource
name="book_details"
list={() => (
<List>
<Datagrid>
<TextField source="id" />
<TextField source="ISBN" />
<ReferenceField
source="book_id"
reference="books"
/>
</Datagrid>
</List>
)}
create={() => (
<Create>
<SimpleForm>
<TextInput source="ISBN" />
<ReferenceInput
source="book_id"
reference="books"
label="Book"
/>
</SimpleForm>
</Create>
)}
/>
</AdminUI>
</AdminContext>
</TestMemoryRouter>
);

export const EmptyWithTranslate = () => (
Expand Down
21 changes: 14 additions & 7 deletions packages/ra-ui-materialui/src/field/ReferenceOneField.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactNode, useMemo } from 'react';
import React, { ReactElement, ReactNode, useMemo } from 'react';
import { UseQueryOptions } from '@tanstack/react-query';
import { Typography } from '@mui/material';
import {
Expand Down Expand Up @@ -73,14 +73,20 @@ export const ReferenceOneField = <
}),
[controllerProps, path]
);
return !record ||
(!controllerProps.isPending &&
controllerProps.referenceRecord == null) ? (
emptyText ? (

const empty =
typeof emptyText === 'string' ? (
<Typography component="span" variant="body2">
{emptyText && translate(emptyText, { _: emptyText })}
</Typography>
) : null
) : emptyText ? (
emptyText
) : null;

return !record ||
(!controllerProps.isPending &&
controllerProps.referenceRecord == null) ? (
empty
) : (
<ResourceContextProvider value={reference}>
<ReferenceFieldContextProvider value={context}>
Expand All @@ -97,14 +103,15 @@ export const ReferenceOneField = <
export interface ReferenceOneFieldProps<
RecordType extends RaRecord = RaRecord,
ReferenceRecordType extends RaRecord = RaRecord,
> extends Omit<FieldProps<RecordType>, 'source'> {
> extends Omit<FieldProps<RecordType>, 'source' | 'emptyText'> {
children?: ReactNode;
reference: string;
target: string;
sort?: SortPayload;
source?: string;
filter?: any;
link?: LinkToType<ReferenceRecordType>;
emptyText?: string | ReactElement;
queryOptions?: Omit<
UseQueryOptions<{
data: ReferenceRecordType[];
Expand Down

0 comments on commit 55c7e58

Please sign in to comment.