Skip to content

Commit 1e773c1

Browse files
authoredApr 27, 2022
Merge pull request #7519 from marmelab/fix-select-onchange
Fix SelectInput / SelectArrayInput onChange handler
2 parents c107fdd + 793b943 commit 1e773c1

File tree

4 files changed

+163
-11
lines changed

4 files changed

+163
-11
lines changed
 

‎packages/ra-ui-materialui/src/input/SelectArrayInput.spec.tsx

+73
Original file line numberDiff line numberDiff line change
@@ -502,4 +502,77 @@ describe('<SelectArrayInput />', () => {
502502
// 2 because there is both the chip for the new selected item and the option (event if hidden)
503503
expect(screen.queryAllByText(newChoice.name).length).toEqual(2);
504504
});
505+
506+
it('should recive an event object on change', async () => {
507+
const choices = [...defaultProps.choices];
508+
const onChange = jest.fn();
509+
510+
render(
511+
<AdminContext dataProvider={testDataProvider()}>
512+
<SimpleForm>
513+
<SelectArrayInput
514+
{...defaultProps}
515+
choices={choices}
516+
onChange={onChange}
517+
/>
518+
</SimpleForm>
519+
</AdminContext>
520+
);
521+
522+
const input = screen.getByLabelText(
523+
'resources.posts.fields.categories'
524+
) as HTMLInputElement;
525+
fireEvent.mouseDown(input);
526+
527+
fireEvent.click(screen.getByText('Lifestyle'));
528+
529+
await waitFor(() => {
530+
expect(onChange).toHaveBeenCalledWith(
531+
expect.objectContaining({ isTrusted: false })
532+
);
533+
});
534+
});
535+
536+
it('should recive a value on change when creating a new choice', async () => {
537+
jest.spyOn(console, 'warn').mockImplementation(() => {});
538+
const choices = [...defaultProps.choices];
539+
const newChoice = { id: 'js_fatigue', name: 'New Kid On The Block' };
540+
const onChange = jest.fn();
541+
542+
const Create = () => {
543+
const context = useCreateSuggestionContext();
544+
const handleClick = () => {
545+
choices.push(newChoice);
546+
context.onCreate(newChoice);
547+
};
548+
549+
return <button onClick={handleClick}>Get the kid</button>;
550+
};
551+
552+
render(
553+
<AdminContext dataProvider={testDataProvider()}>
554+
<SimpleForm onSubmit={jest.fn()}>
555+
<SelectArrayInput
556+
{...defaultProps}
557+
choices={choices}
558+
create={<Create />}
559+
onChange={onChange}
560+
/>
561+
</SimpleForm>
562+
</AdminContext>
563+
);
564+
565+
const input = screen.getByLabelText(
566+
'resources.posts.fields.categories'
567+
) as HTMLInputElement;
568+
fireEvent.mouseDown(input);
569+
570+
fireEvent.click(screen.getByText('ra.action.create'));
571+
fireEvent.click(screen.getByText('Get the kid'));
572+
input.blur();
573+
574+
await waitFor(() => {
575+
expect(onChange).toHaveBeenCalledWith(['js_fatigue']);
576+
});
577+
});
505578
});

‎packages/ra-ui-materialui/src/input/SelectArrayInput.tsx

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react';
22
import { styled } from '@mui/material/styles';
3-
import { useCallback, useRef } from 'react';
3+
import { useCallback, useRef, ChangeEvent } from 'react';
44
import PropTypes from 'prop-types';
55
import clsx from 'clsx';
66
import {
@@ -17,6 +17,7 @@ import {
1717
useInput,
1818
useChoicesContext,
1919
useChoices,
20+
RaRecord,
2021
} from 'ra-core';
2122
import { InputHelperText } from './InputHelperText';
2223
import { FormControlProps } from '@mui/material/FormControl';
@@ -143,12 +144,11 @@ export const SelectArrayInput = (props: SelectArrayInputProps) => {
143144
});
144145

145146
const handleChange = useCallback(
146-
(eventOrChoice: any) => {
147+
(eventOrChoice: ChangeEvent<HTMLInputElement> | RaRecord) => {
147148
// We might receive an event from the mui component
148149
// In this case, it will be the choice id
149-
// eslint-disable-next-line eqeqeq
150-
if (eventOrChoice?.target?.value != undefined) {
151-
field.onChange(eventOrChoice.target.value);
150+
if (eventOrChoice?.target) {
151+
field.onChange(eventOrChoice);
152152
} else {
153153
// Or we might receive a choice directly, for instance a newly created one
154154
field.onChange([
@@ -295,6 +295,7 @@ export type SelectArrayInputProps = ChoicesProps &
295295
Omit<FormControlProps, 'defaultValue' | 'onBlur' | 'onChange'> & {
296296
disableValue?: string;
297297
source?: string;
298+
onChange?: (event: ChangeEvent<HTMLInputElement> | RaRecord) => void;
298299
};
299300

300301
SelectArrayInput.propTypes = {

‎packages/ra-ui-materialui/src/input/SelectInput.spec.tsx

+77
Original file line numberDiff line numberDiff line change
@@ -589,4 +589,81 @@ describe('<SelectInput />', () => {
589589
expect(screen.queryByText(newChoice.name)).not.toBeNull();
590590
});
591591
});
592+
593+
it('should recive an event object on change', async () => {
594+
const choices = [...defaultProps.choices];
595+
const onChange = jest.fn();
596+
597+
render(
598+
<AdminContext dataProvider={testDataProvider()}>
599+
<SimpleForm>
600+
<SelectInput
601+
{...defaultProps}
602+
choices={choices}
603+
defaultValue="ang"
604+
inputProps={{ 'data-testid': 'content-input' }}
605+
onChange={onChange}
606+
/>
607+
</SimpleForm>
608+
</AdminContext>
609+
);
610+
611+
const input = screen.getByTestId('content-input');
612+
fireEvent.change(input, {
613+
target: { value: 'rea' },
614+
});
615+
616+
await waitFor(() => {
617+
expect(onChange).toHaveBeenCalledWith(
618+
expect.objectContaining({
619+
bubbles: true,
620+
cancelable: false,
621+
currentTarget: null,
622+
eventPhase: 3,
623+
isTrusted: false,
624+
type: 'change',
625+
})
626+
);
627+
});
628+
});
629+
630+
it('should recive a value on change when creating a new choice', async () => {
631+
jest.spyOn(console, 'warn').mockImplementation(() => {});
632+
const choices = [...defaultProps.choices];
633+
const newChoice = { id: 'js_fatigue', name: 'New Kid On The Block' };
634+
const onChange = jest.fn();
635+
636+
const Create = () => {
637+
const context = useCreateSuggestionContext();
638+
const handleClick = () => {
639+
choices.push(newChoice);
640+
context.onCreate(newChoice);
641+
};
642+
643+
return <button onClick={handleClick}>Get the kid</button>;
644+
};
645+
646+
render(
647+
<AdminContext dataProvider={testDataProvider()}>
648+
<SimpleForm onSubmit={jest.fn()}>
649+
<SelectInput
650+
{...defaultProps}
651+
choices={choices}
652+
create={<Create />}
653+
onChange={onChange}
654+
/>
655+
</SimpleForm>
656+
</AdminContext>
657+
);
658+
659+
const input = screen.getByLabelText('resources.posts.fields.language');
660+
fireEvent.mouseDown(input);
661+
662+
fireEvent.click(screen.getByText('ra.action.create'));
663+
fireEvent.click(screen.getByText('Get the kid'));
664+
665+
await waitFor(() => {
666+
expect(onChange).toHaveBeenCalledWith('js_fatigue');
667+
});
668+
});
592669
});

‎packages/ra-ui-materialui/src/input/SelectInput.tsx

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { ReactElement, useCallback } from 'react';
2+
import { ReactElement, useCallback, ChangeEvent } from 'react';
33
import PropTypes from 'prop-types';
44
import clsx from 'clsx';
55
import MenuItem from '@mui/material/MenuItem';
@@ -12,6 +12,7 @@ import {
1212
useTranslate,
1313
ChoicesProps,
1414
useChoices,
15+
RaRecord,
1516
} from 'ra-core';
1617

1718
import { CommonInputProps } from './CommonInputProps';
@@ -183,12 +184,11 @@ export const SelectInput = (props: SelectInputProps) => {
183184
]);
184185

185186
const handleChange = useCallback(
186-
async (eventOrChoice: any) => {
187+
async (eventOrChoice: ChangeEvent<HTMLInputElement> | RaRecord) => {
187188
// We might receive an event from the mui component
188189
// In this case, it will be the choice id
189-
// eslint-disable-next-line eqeqeq
190-
if (eventOrChoice?.target?.value != undefined) {
191-
field.onChange(eventOrChoice.target.value);
190+
if (eventOrChoice?.target) {
191+
field.onChange(eventOrChoice);
192192
} else {
193193
// Or we might receive a choice directly, for instance a newly created one
194194
field.onChange(getChoiceValue(eventOrChoice));
@@ -373,10 +373,11 @@ const StyledResettableTextField = styled(ResettableTextField, {
373373
export type SelectInputProps = Omit<CommonInputProps, 'source'> &
374374
ChoicesProps &
375375
Omit<SupportCreateSuggestionOptions, 'handleChange'> &
376-
Omit<TextFieldProps, 'label' | 'helperText' | 'classes'> & {
376+
Omit<TextFieldProps, 'label' | 'helperText' | 'classes' | 'onChange'> & {
377377
disableValue?: string;
378378
emptyText?: string | ReactElement;
379379
emptyValue?: any;
380380
// Source is optional as AutocompleteInput can be used inside a ReferenceInput that already defines the source
381381
source?: string;
382+
onChange?: (event: ChangeEvent<HTMLInputElement> | RaRecord) => void;
382383
};

0 commit comments

Comments
 (0)
Please sign in to comment.