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

Update <SelectInput> to support an array of strings as choices #10038

Merged
merged 2 commits into from
Jul 23, 2024
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
10 changes: 5 additions & 5 deletions docs/AutocompleteArrayInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,13 @@ If you need to *fetch* the options from another resource, you're actually editin
</ReferenceArrayInput>
```

If you have an *array of values* for the options, turn it into an array of objects with the `id` and `name` properties:
You can also pass an *array of strings* for the choices:

```jsx
const possibleValues = ['programming', 'lifestyle', 'photography'];
const ucfirst = name => name.charAt(0).toUpperCase() + name.slice(1);
const choices = possibleValues.map(value => ({ id: value, name: ucfirst(value) }));

const roles = ['Admin', 'Editor', 'Moderator', 'Reviewer'];
<AutocompleteArrayInput source="roles" choices={roles} />
// is equivalent to
const choices = roles.map(value => ({ id: value, name: value }));
<AutocompleteArrayInput source="roles" choices={choices} />
```

Expand Down
10 changes: 5 additions & 5 deletions docs/AutocompleteInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,13 @@ If you need to *fetch* the options from another resource, you're usually editing

See [Selecting a foreign key](#selecting-a-foreign-key) below for more information.

If you have an *array of values* for the options, turn it into an array of objects with the `id` and `name` properties:
You can also pass an *array of strings* for the choices:

```jsx
const possibleValues = ['tech', 'lifestyle', 'people'];
const ucfirst = name => name.charAt(0).toUpperCase() + name.slice(1);
const choices = possibleValues.map(value => ({ id: value, name: ucfirst(value) }));

const categories = ['tech', 'lifestyle', 'people'];
<AutocompleteInput source="category" choices={categories} />
// is equivalent to
const choices = categories.map(value => ({ id: value, name: value }));
<AutocompleteInput source="category" choices={choices} />
```

Expand Down
10 changes: 5 additions & 5 deletions docs/CheckboxGroupInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ If you need to *fetch* the options from another resource, you're actually editin
</ReferenceArrayInput>
```

If you have an *array of values* for the options, turn it into an array of objects with the `id` and `name` properties:
You can also pass an *array of strings* for the choices:

```jsx
const possibleValues = ['programming', 'lifestyle', 'photography'];
const ucfirst = name => name.charAt(0).toUpperCase() + name.slice(1);
const choices = possibleValues.map(value => ({ id: value, name: ucfirst(value) }));

const roles = ['Admin', 'Editor', 'Moderator', 'Reviewer'];
<CheckboxGroupInput source="roles" choices={roles} />
// is equivalent to
const choices = roles.map(value => ({ id: value, name: value }));
<CheckboxGroupInput source="roles" choices={choices} />
```

Expand Down
10 changes: 5 additions & 5 deletions docs/RadioButtonGroupInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,13 @@ If you need to *fetch* the options from another resource, you're actually editin

See [Selecting a foreign key](#selecting-a-foreign-key) below for more information.

If you have an *array of values* for the options, turn it into an array of objects with the `id` and `name` properties:
You can also pass an *array of strings* for the choices:

```jsx
const possibleValues = ['tech', 'lifestyle', 'people'];
const ucfirst = name => name.charAt(0).toUpperCase() + name.slice(1);
const choices = possibleValues.map(value => ({ id: value, name: ucfirst(value) }));

const categories = ['tech', 'lifestyle', 'people'];
<RadioButtonGroupInput source="category" choices={categories} />
// is equivalent to
const choices = categories.map(value => ({ id: value, name: value }));
<RadioButtonGroupInput source="category" choices={choices} />
```

Expand Down
10 changes: 5 additions & 5 deletions docs/SelectArrayInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,13 @@ If you need to *fetch* the options from another resource, you're actually editin
</ReferenceArrayInput>
```

If you have an *array of values* for the options, turn it into an array of objects with the `id` and `name` properties:
You can also pass an *array of strings* for the choices:

```jsx
const possibleValues = ['programming', 'lifestyle', 'photography'];
const ucfirst = name => name.charAt(0).toUpperCase() + name.slice(1);
const choices = possibleValues.map(value => ({ id: value, name: ucfirst(value) }));

const roles = ['Admin', 'Editor', 'Moderator', 'Reviewer'];
<SelectArrayInput source="roles" choices={roles} />
// is equivalent to
const choices = roles.map(value => ({ id: value, name: value }));
<SelectArrayInput source="roles" choices={choices} />
```

Expand Down
10 changes: 5 additions & 5 deletions docs/SelectInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,13 @@ If you need to *fetch* the options from another resource, you're actually editin

See [Selecting a foreign key](#selecting-a-foreign-key) below for more information.

If you have an *array of values* for the options, turn it into an array of objects with the `id` and `name` properties:
You can also pass an *array of strings* for the choices:

```jsx
const possibleValues = ['tech', 'lifestyle', 'people'];
const ucfirst = name => name.charAt(0).toUpperCase() + name.slice(1);
const choices = possibleValues.map(value => ({ id: value, name: ucfirst(value) }));

const categories = ['tech', 'lifestyle', 'people'];
<SelectInput source="category" choices={categories} />
// is equivalent to
const choices = categories.map(value => ({ id: value, name: value }));
<SelectInput source="category" choices={choices} />
```

Expand Down
22 changes: 19 additions & 3 deletions packages/ra-core/src/form/choices/useChoicesContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ import { useList } from '../../controller';
import { ChoicesContext, ChoicesContextValue } from './ChoicesContext';

export const useChoicesContext = <ChoicesType extends RaRecord = RaRecord>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised this doesn't trigger a TS error as RaRecord can't be a string. I believe this is because ChoicesProps in useChoices accepts any[] for choices. This might be an issue later

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't type the choices as users may pass anything and set the optionText and optionValue. It's never been an issue ;)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not restrict the ChoicesType then but let's do that in another PR

options: Partial<ChoicesContextValue> & { choices?: ChoicesType[] } = {}
options: Partial<ChoicesContextValue> & {
choices?: ChoicesType[];
} = {}
): ChoicesContextValue<ChoicesType> => {
const context = useContext(
ChoicesContext
) as ChoicesContextValue<ChoicesType>;
const choices =
options.choices && isArrayOfStrings(options.choices)
? convertOptionsToChoices(options.choices)
: options.choices;
// @ts-ignore cannot satisfy the type of useList because of ability to pass partial options
const { data, ...list } = useList<ChoicesType>({
data: options.choices,
const { data, ...list } = useList<any>({
data: choices,
isLoading: options.isLoading ?? false,
isPending: options.isPending ?? false,
isFetching: options.isFetching ?? false,
Expand Down Expand Up @@ -58,3 +64,13 @@ export const useChoicesContext = <ChoicesType extends RaRecord = RaRecord>(

return result as ChoicesContextValue<ChoicesType>;
};

const isArrayOfStrings = (choices: any[]): choices is string[] =>
Array.isArray(choices) &&
choices.every(choice => typeof choice === 'string');

const convertOptionsToChoices = (options: string[]) =>
options.map(choice => ({
id: choice,
name: choice,
}));
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ export const Basic = () => (
</Wrapper>
);

export const StringChoices = () => (
<Wrapper>
<AutocompleteArrayInput
source="roles"
choices={['Admin', 'Editor', 'Moderator', 'Reviewer']}
/>
</Wrapper>
);

export const OnChange = ({
onChange = (value, records) => console.log({ value, records }),
}: Pick<AutocompleteArrayInputProps, 'onChange'>) => (
Expand Down
15 changes: 15 additions & 0 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,21 @@ export const Basic = ({ onSuccess = console.log }) => (
</Wrapper>
);

export const StringChoices = () => (
<Wrapper>
<AutocompleteInput
source="author"
choices={[
'Leo Tolstoy',
'Victor Hugo',
'William Shakespeare',
'Charles Baudelaire',
'Marcel Proust',
]}
/>
</Wrapper>
);

export const Required = () => (
<Wrapper>
<AutocompleteInput
Expand Down
Loading
Loading