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

Implement emptyText and emptyValue in AutocompleteInput #8162

Merged
merged 37 commits into from
Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a8da321
Remove emptyValue prop from SelectInput
WiXSL Sep 14, 2022
5910a5c
Remove emptyValue and emptyText props from AutocompleteInput
WiXSL Sep 14, 2022
f89978d
Update docs
WiXSL Sep 14, 2022
987ce9d
Revert changes
WiXSL Sep 14, 2022
74d79a2
Add emptyText and emptyValue
Sep 19, 2022
fb26ac4
Revert changes
Sep 19, 2022
fb800dc
Remove unnecessary change
Sep 19, 2022
0d81e78
Fx empty string and i18n for emptyText
WiXSL Sep 20, 2022
91302a2
Remove emptyValue / emptyText from useSuggestions
WiXSL Sep 20, 2022
ee5c76f
remove emptyValue
WiXSL Sep 20, 2022
6ec40ac
Remove emptyValue from the docs
WiXSL Sep 20, 2022
a5ca08a
Add test
WiXSL Sep 20, 2022
7c5b1f5
Improve test description
WiXSL Sep 20, 2022
b81c9d5
Use optionValue and optionText for empty choice
WiXSL Sep 22, 2022
8f56320
Throw error on optionText of type React element when emptyText is passed
WiXSL Sep 22, 2022
837f931
Added back emptyValue
WiXSL Sep 23, 2022
0c80558
Improve stories
WiXSL Sep 23, 2022
b920860
Improve docs
WiXSL Sep 23, 2022
202d30c
Fix docs of SelectInput
WiXSL Sep 23, 2022
8663d24
Revert emptyText evaluation
WiXSL Sep 23, 2022
fb886c0
Update docs/AutocompleteInput.md
WiXSL Sep 23, 2022
b0e3f1b
Update docs/AutocompleteInput.md
WiXSL Sep 23, 2022
8b3d5b9
Update docs/SelectInput.md
WiXSL Sep 23, 2022
6e83ea5
Update docs/AutocompleteInput.md
WiXSL Sep 23, 2022
01ff3df
Applied review
WiXSL Sep 23, 2022
983231d
Fix last commit
Sep 23, 2022
971e59e
Fix typo
Sep 23, 2022
3e02749
Improve stories
Sep 26, 2022
4bc2e38
Prevent emptyText/emptyValue usage for AutocompleteArrayInput
Sep 26, 2022
d291380
set emptyText as an empty string by default
Sep 26, 2022
02ebecc
Use emptyValue as default value for the input
Sep 26, 2022
4621add
Fix test
Sep 26, 2022
a127352
Fix tests
Sep 26, 2022
23717f7
Fix tests
Sep 26, 2022
04da982
Fix e2e test
Sep 26, 2022
5f009c6
Fix missing dependencies in useCallback
Sep 26, 2022
abf75e1
Update docs/SelectInput.md
WiXSL Sep 28, 2022
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
23 changes: 21 additions & 2 deletions docs/AutocompleteInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ import { AutocompleteInput, ReferenceInput } from 'react-admin';
| `choices` | Optional | `Object[]` | `-` | List of items to autosuggest. Required if not inside a ReferenceInput. |
| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice |
| `createItemLabel` | Optional | `string` | `ra.action.create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty |
| `emptyValue` | Optional | `any` | `''` | The value to use for the empty element |
| `emptyText` | Optional | `string` | `''` | The text to use for the empty element |
| `emptyValue` | Optional | `any` | `''` | The value to use for the empty element |
| `matchSuggestion` | Optional | `Function` | `-` | Required if `optionText` is a React element. Function returning a boolean indicating whether a choice matches the filter. `(filter, choice) => boolean` |
| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. |
| `optionText` | Optional | `string` | `Function` | `Component` | `name` | Field name of record to display in the suggestion item or function which accepts the correct record as argument (`(record)=> {string}`) |
| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
| `inputText` | Optional | `Function` | `-` | Required if `optionText` is a custom Component, this function must return the text displayed for the current selection. |
| `filterToQuery` | Optional | `string` => `Object` | `searchText => ({ q: [searchText] })` | How to transform the searchText into a parameter for the data provider |
| `setFilter` | Optional | `Function` | `null` | A callback to inform the `searchText` has changed and new `choices` can be retrieved based on this `searchText`. Signature `searchText => void`. This function is automatically set up when using `ReferenceInput`. |
| `setFilter` | Optional | `Function` | `null` | A callback to inform the `searchText` has changed and new `choices` can be retrieved based on this `searchText`. Signature `searchText => void`. This function is automatically set up when using `ReferenceInput`. |
| `shouldRenderSuggestions` | Optional | `Function` | `() => true` | A function that returns a `boolean` to determine whether or not suggestions are rendered. Use this when working with large collections of data to improve performance and user experience. This function is passed into the underlying react-autosuggest component. Ex.`(value) => value.trim().length > 2` |
| `suggestionLimit` | Optional | `number` | `null` | Limits the numbers of suggestions that are shown in the dropdown list |

Expand Down Expand Up @@ -97,6 +97,25 @@ When used inside a `<ReferenceInput>`, `<AutocompleteInput>` doesn't need a `cho
See [Using in a `ReferenceInput>`](#using-in-a-referenceinput) below for more information.


## `emptyValue`

An empty choice is always added (with a default `''` value, which you can override with the `emptyValue` prop) on top of the options. You can furthermore customize the empty choice by using the `emptyText` prop, which can receive a string or a React Element.

```jsx
<AutocompleteInput
source="author_id"
emptyValue={0}
emptyText="No author"
choices={[
{ id: 123, name: 'Leo Tolstoi' },
{ id: 456, name: 'Jane Austen' },
]}
/>
```

**Note**: `emptyValue` cannot be set to `undefined` or `null` since the `dataProvider` method will receive an empty string on submit due to the nature of HTML inputs.


## `optionText`

You can customize the choice field to use for the option name, thanks to the `optionText` attribute:
Expand Down
8 changes: 5 additions & 3 deletions docs/SelectInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,18 @@ const choices = [

## `emptyValue`

An empty choice is always added (with a default `''` value, which you can overwrite with the `emptyValue` prop) on top of the options. You can furthermore customize the `MenuItem` for the empty choice by using the `emptyText` prop, which can receive either a string or a React Element, which doesn't receive any props.
An empty choice is always added (with a default `''` value, which you can override with the `emptyValue` prop) on top of the options. You can furthermore customize the `MenuItem` for the empty choice by using the `emptyText` prop, which can receive either a string or a React Element, which doesn't receive any props.

```jsx
<SelectInput source="category" emptyValue={null} choices={[
<SelectInput source="category" emptyValue="" choices={[
WiXSL marked this conversation as resolved.
Show resolved Hide resolved
{ id: 'programming', name: 'Programming' },
{ id: 'lifestyle', name: 'Lifestyle' },
{ id: 'photography', name: 'Photography' },
]} />
```

**Note**: `emptyValue` cannot be set to `undefined` or `null` since the `dataProvider` method will receive an empty string on submit due to the nature of HTML inputs.

## `options`

Use the `options` attribute if you want to override any of MUI's `<SelectField>` attributes:
Expand Down Expand Up @@ -364,4 +366,4 @@ const CreateCategory = () => {
);
};
```
{% endraw %}
{% endraw %}
16 changes: 2 additions & 14 deletions packages/ra-core/src/form/useSuggestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@ import { useTranslate } from '../i18n';
*
* @param allowDuplicates A boolean indicating whether a suggestion can be added several times
* @param choices An array of available choices
* @param emptyText The text to use for the empty suggestion. Defaults to an empty string
* @param emptyValue The value to use for the empty suggestion. Defaults to `null`
* @param limitChoicesToValue A boolean indicating whether the initial suggestions should be limited to the currently selected one(s)
* @param matchSuggestion Optional unless `optionText` is a React element. Function which check whether a choice matches a filter. Must return a boolean.
* @param optionText Either a string defining the property to use to get the choice text, a function or a React element
* @param optionValue The property to use to get the choice value
* @param selectedItem The currently selected item. May be an array of selected items
* @param selectedItem The currently selected item. Maybe an array of selected items
* @param suggestionLimit The maximum number of suggestions returned
* @param translateChoice A boolean indicating whether to option text should be translated
*
Expand All @@ -28,8 +26,6 @@ export const useSuggestions = ({
choices,
createText = 'ra.action.create',
createValue = '@@create',
emptyText = '',
emptyValue = null,
limitChoicesToValue,
matchSuggestion,
optionText,
Expand All @@ -52,8 +48,6 @@ export const useSuggestions = ({
choices,
createText,
createValue,
emptyText: translate(emptyText, { _: emptyText }),
emptyValue,
getChoiceText,
getChoiceValue,
limitChoicesToValue,
Expand All @@ -68,8 +62,6 @@ export const useSuggestions = ({
choices,
createText,
createValue,
emptyText,
emptyValue,
getChoiceText,
getChoiceValue,
limitChoicesToValue,
Expand Down Expand Up @@ -98,8 +90,6 @@ export interface UseSuggestionsOptions extends UseChoicesOptions {
choices: any[];
createText?: string;
createValue?: any;
emptyText?: string;
emptyValue?: any;
limitChoicesToValue?: boolean;
matchSuggestion?: (
filter: string,
Expand Down Expand Up @@ -160,8 +150,6 @@ export const getSuggestionsFactory = ({
choices = [],
createText = 'ra.action.create',
createValue = '@@create',
emptyText = '',
emptyValue = null,
optionText = 'name',
optionValue = 'id',
getChoiceText,
Expand Down Expand Up @@ -263,7 +251,7 @@ const limitSuggestions = (suggestions: any[], limit: any = 0) =>
* [{ id: 1, name: 'foo'}, { id: 2, name: 'bar' }],
* );
*
* // Will return [{ id: null, name: '' }, { id: 1, name: 'foo' }, , { id: 2, name: 'bar' }]
* // Will return [{ id: null, name: '' }, { id: 1, name: 'foo' }, { id: 2, name: 'bar' }]
*
* @param suggestions List of suggestions
* @param options
Expand Down
29 changes: 28 additions & 1 deletion packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,33 @@ describe('<AutocompleteInput />', () => {
expect(screen.queryByDisplayValue('foo')).not.toBeNull();
});

describe('emptyText', () => {
it('should allow to have an empty menu option text by passing a string', () => {
const emptyText = 'Default';

render(
<AdminContext dataProvider={testDataProvider()}>
<SimpleForm onSubmit={jest.fn()}>
<AutocompleteInput
emptyText={emptyText}
{...defaultProps}
choices={[{ id: 2, name: 'foo' }]}
/>
</SimpleForm>
</AdminContext>
);
fireEvent.mouseDown(
screen.getByLabelText('resources.users.fields.role')
);

expect(screen.queryAllByRole('option').length).toEqual(1);

const input = screen.getByRole('textbox') as HTMLInputElement;

expect(input.value).toEqual('Default');
});
});

describe('optionValue', () => {
it('should use optionValue as value identifier', async () => {
render(
Expand Down Expand Up @@ -919,7 +946,7 @@ describe('<AutocompleteInput />', () => {
fireEvent.change(input, { target: { value: 'foo' } });
await waitFor(
() => {
expect(screen.getByRole('listbox').children).toHaveLength(1);
expect(screen.queryAllByRole('option')).toHaveLength(1);
},
{ timeout: 2000 }
);
Expand Down
77 changes: 77 additions & 0 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ const dataProvider = {
update: (resource, params) => Promise.resolve(params),
} as any;

const dataProviderEmpty = {
getOne: (resource, params) =>
Promise.resolve({
data: {
id: 1,
title: 'War and Peace',
author: 1,
authorNone: 1,
authorEmpty: 1,
authorZero: 1,
summary:
"War and Peace broadly focuses on Napoleon's invasion of Russia, and the impact it had on Tsarist society. The book explores themes such as revolution, revolution and empire, the growth and decline of various states and the impact it had on their economies, culture, and society.",
year: 1869,
},
}),
update: (resource, params) => Promise.resolve(params),
} as any;

const history = createMemoryHistory({ initialEntries: ['/books/1'] });

const BookEdit = () => {
Expand Down Expand Up @@ -607,3 +625,62 @@ export const VeryLargeOptionsNumber = () => {
</Admin>
);
};

const BookEditWithEmptyText = () => {
const choices = [
{ id: 1, name: 'Leo Tolstoy' },
{ id: 2, name: 'Victor Hugo' },
{ id: 3, name: 'William Shakespeare' },
{ id: 4, name: 'Charles Baudelaire' },
{ id: 5, name: 'Marcel Proust' },
];
return (
<Edit
mutationMode="pessimistic"
mutationOptions={{
onSuccess: data => {
console.log(data);
},
}}
>
<SimpleForm>
<AutocompleteInput
label="emptyValue set to 'no-author', emptyText set to '' by default"
source="author"
choices={choices}
emptyValue="no-author"
fullWidth
/>
<AutocompleteInput
label="emptyValue set to 'none'"
source="authorNone"
choices={choices}
emptyValue="none"
emptyText="- No author - "
fullWidth
/>
<AutocompleteInput
label="emptyValue set to ''"
source="authorEmpty"
choices={choices}
emptyText="- No author - "
fullWidth
/>
<AutocompleteInput
label="emptyValue set to 0"
source="authorZero"
choices={choices}
emptyValue={0}
emptyText="- No author - "
fullWidth
/>
</SimpleForm>
</Edit>
);
};

export const EmptyText = () => (
<Admin dataProvider={dataProviderEmpty} history={history}>
<Resource name="books" edit={BookEditWithEmptyText} />
</Admin>
);
Loading