Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix AutocompleteInputcreate text is undefined when using a function as option text #7908

Merged
merged 4 commits into from
Jul 6, 2022
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
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