Skip to content

Commit

Permalink
Merge pull request #7908 from marmelab/7903-autocompletearrayinput-de…
Browse files Browse the repository at this point in the history
…fault-value

Fix `AutocompleteInput`create text is undefined when using a function as option text
  • Loading branch information
djhi authored Jul 6, 2022
2 parents b17116b + 4dbaebe commit 5a8947b
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,7 @@ describe('<AutocompleteArrayInput />', () => {
resource="posts"
choices={choices}
onCreate={handleCreate}
optionText={choice => `Choice is ${choice.name}`}
optionText={choice => `Choice is not displayed`}
/>
</SimpleForm>
</AdminContext>
Expand All @@ -839,7 +839,7 @@ describe('<AutocompleteArrayInput />', () => {
}) as HTMLInputElement;
input.focus();
fireEvent.change(input, { target: { value: 'New Kid On The Block' } });
fireEvent.click(screen.getByText('Choice is ra.action.create_item'));
fireEvent.click(screen.getByText('ra.action.create_item'));
await new Promise(resolve => setTimeout(resolve));
rerender(
<AdminContext dataProvider={testDataProvider()}>
Expand Down
64 changes: 64 additions & 0 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,41 @@ describe('<AutocompleteInput />', () => {
});
});

it('should not use optionText defined with a function value on the "create new item" option', async () => {
const choices = [
{ id: 'ang', fullname: 'Angular' },
{ id: 'rea', fullname: 'React' },
];
const optionText = jest.fn(choice => choice.fullname);

const handleCreate = filter => ({
id: 'newid',
fullname: filter,
});

render(
<AdminContext dataProvider={testDataProvider()}>
<SimpleForm mode="onBlur" onSubmit={jest.fn()}>
<AutocompleteInput
source="language"
resource="posts"
choices={choices}
optionText={optionText}
onCreate={handleCreate}
/>
</SimpleForm>
</AdminContext>
);

const input = screen.getByLabelText(
'resources.posts.fields.language'
) as HTMLInputElement;
input.focus();
fireEvent.change(input, { target: { value: 'Vue' } });
await new Promise(resolve => setTimeout(resolve));
expect(screen.getByText('ra.action.create_item')).not.toBeNull();
});

it('should translate the value by default', async () => {
render(
<AdminContext dataProvider={testDataProvider()}>
Expand Down Expand Up @@ -396,6 +431,35 @@ describe('<AutocompleteInput />', () => {
).not.toBeNull();
});

it('should allow customized rendering of option items', () => {
const OptionItem = props => {
const record = useRecordContext();
return <div {...props} aria-label={record && record.name} />;
};

render(
<AdminContext dataProvider={testDataProvider()}>
<SimpleForm onSubmit={jest.fn()} defaultValues={{ role: 2 }}>
<AutocompleteInput
{...defaultProps}
optionText={<OptionItem />}
matchSuggestion={() => true}
inputText={record => record?.name}
choices={[
{ id: 1, name: 'bar' },
{ id: 2, name: 'foo' },
]}
/>
</SimpleForm>
</AdminContext>
);

const input = screen.getByLabelText('resources.users.fields.role');
fireEvent.focus(input);

expect(screen.queryByLabelText('bar')).not.toBeNull();
});

it('should reset filter when input value changed', async () => {
const setFilter = jest.fn();
const { rerender } = render(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { Admin } from 'react-admin';
import { Admin, AdminContext } from 'react-admin';
import { Resource, required, useCreate, useRecordContext } from 'ra-core';
import { createMemoryHistory } from 'history';
import {
Expand Down Expand Up @@ -420,6 +420,36 @@ export const InsideReferenceInputWithCreationSupport = () => (
</Admin>
);

const OptionItem = props => {
const record = useRecordContext();
return (
<div {...props} aria-label={record && record.name}>
{`from optionText: ${record && record.name}`}
</div>
);
};

export const CustomizedItemRendering = () => {
return (
<AdminContext dataProvider={dataProviderWithAuthors}>
<SimpleForm onSubmit={() => {}} defaultValues={{ role: 2 }}>
<AutocompleteInput
fullWidth
source="role"
resource="users"
optionText={<OptionItem />}
inputText={record => `from inputText ${record?.name}`}
matchSuggestion={() => true}
choices={[
{ id: 1, name: 'bar' },
{ id: 2, name: 'foo' },
]}
/>
</SimpleForm>
</AdminContext>
);
};

const DalmatianEdit = () => {
const dalmatians: any[] = [];
for (let index = 0; index < 1100; index++) {
Expand Down
15 changes: 10 additions & 5 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ If you provided a React element for the optionText prop, you must also provide t
getCreateItem,
handleChange: handleChangeWithCreateSupport,
createElement,
createId,
} = useSupportCreateSuggestion({
create,
createLabel,
Expand All @@ -314,24 +315,28 @@ If you provided a React element for the optionText prop, you must also provide t
});

const getOptionLabel = useCallback(
(option: any) => {
(option: any, isListItem: boolean = false) => {
// eslint-disable-next-line eqeqeq
if (option == undefined) {
return '';
}

// Value selected with enter, right from the input
if (typeof option === 'string') {
return option;
}

// eslint-disable-next-line eqeqeq
if (inputText != undefined) {
if (option?.id === createId) {
return option?.name;
}

if (!isListItem && inputText !== undefined) {
return inputText(option);
}

return getChoiceText(option);
},
[getChoiceText, inputText]
[getChoiceText, inputText, createId]
);

useEffect(() => {
Expand Down Expand Up @@ -527,7 +532,7 @@ If you provided a React element for the optionText prop, you must also provide t
onBlur={field.onBlur}
onInputChange={handleInputChange}
renderOption={(props, record) => (
<li {...props}>{getChoiceText(record)}</li>
<li {...props}>{getOptionLabel(record, true)}</li>
)}
/>
{createElement}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const useSupportCreateSuggestion = (
);

return {
createId: createValue,
getCreateItem: () => {
if (typeof optionText !== 'string') {
return {
Expand Down Expand Up @@ -129,6 +130,7 @@ export interface SupportCreateSuggestionOptions {
}

export interface UseSupportCreateValue {
createId: string;
getCreateItem: (
filterValue?: string
) => { id: Identifier; [key: string]: any };
Expand Down

0 comments on commit 5a8947b

Please sign in to comment.