🐛(react) fix controlled searchable select

Changing the controlled value was setting triggering immediately an
onChange event with undefined value. This was due to the fact that when
the controlled value was changed SelectMonoAux was searching only in
options displayed, where it should in reality be searching accross all
options.

fixes #162
This commit is contained in:
Nathan Vasse
2023-09-15 11:00:44 +02:00
committed by NathanVss
parent 4616ad9ffb
commit fd988c03e1
6 changed files with 53 additions and 13 deletions

View File

@@ -0,0 +1,5 @@
---
"@openfun/cunningham-react": patch
---
fix controlled searchable select triggering onChange undefined

View File

@@ -1,4 +1,4 @@
import React, { HTMLAttributes, useEffect } from "react";
import React, { HTMLAttributes } from "react";
import { UseSelectReturnValue } from "downshift";
import classNames from "classnames";
import { useCunningham } from ":/components/Provider";
@@ -73,17 +73,6 @@ export const SelectMonoAux = ({
const { t } = useCunningham();
const labelProps = downshiftReturn.getLabelProps();
// When component is controlled, this useEffect will update the local selected item.
useEffect(() => {
if (downshiftProps.initialSelectedItem !== undefined) {
return;
}
const optionToSelect = options.find(
(option) => optionToValue(option) === value,
);
downshiftReturn.selectItem(optionToSelect ?? null);
}, [value]);
return (
<Field state={state} {...props}>
<div

View File

@@ -4,6 +4,7 @@ import { useCunningham } from ":/components/Provider";
import {
getOptionsFilter,
optionToString,
optionToValue,
SelectMonoAux,
SubProps,
} from ":/components/Forms/Select/mono-common";
@@ -40,6 +41,17 @@ export const SelectMonoSearchable = (props: SubProps) => {
downshiftReturn.inputValue,
]);
// When component is controlled, this useEffect will update the local selected item.
useEffect(() => {
if (props.downshiftProps.initialSelectedItem !== undefined) {
return;
}
const optionToSelect = props.options.find(
(option) => optionToValue(option) === props.value,
);
downshiftReturn.selectItem(optionToSelect ?? null);
}, [props.value, props.options, props.downshiftProps]);
const inputProps = downshiftReturn.getInputProps({
ref: inputRef,
disabled: props.disabled,

View File

@@ -1,7 +1,8 @@
import { useSelect } from "downshift";
import React from "react";
import React, { useEffect } from "react";
import {
optionToString,
optionToValue,
SelectMonoAux,
SubProps,
} from ":/components/Forms/Select/mono-common";
@@ -13,6 +14,17 @@ export const SelectMonoSimple = (props: SubProps) => {
itemToString: optionToString,
});
// When component is controlled, this useEffect will update the local selected item.
useEffect(() => {
if (props.downshiftProps.initialSelectedItem !== undefined) {
return;
}
const optionToSelect = props.options.find(
(option) => optionToValue(option) === props.value,
);
downshiftReturn.selectItem(optionToSelect ?? null);
}, [props.value, props.options, props.downshiftProps]);
return (
<SelectMonoAux
{...props}

View File

@@ -295,6 +295,7 @@ describe("<Select/>", () => {
<div>
<div>Value = {value}|</div>
<Button onClick={() => setValue(undefined)}>Clear</Button>
<Button onClick={() => setValue("paris")}>Set Paris</Button>
<Select
label="City"
options={[
@@ -367,6 +368,15 @@ describe("<Select/>", () => {
// Make sure value is cleared.
screen.getByText("Value = |");
// Make sure setting value works
const buttonParis = screen.getByRole("button", {
name: "Set Paris",
});
await user.click(buttonParis);
screen.getByText("Value = paris|");
expect(input).toHaveValue("Paris");
});
it("renders disabled", async () => {
render(

View File

@@ -87,6 +87,12 @@ export const Controlled = () => {
onChange={(e) => setValue(e.target.value as string)}
/>
<Button onClick={() => setValue("")}>Reset</Button>
<Button onClick={() => setValue(OPTIONS[0].value)}>
Set {OPTIONS[0].label}
</Button>
<Button onClick={() => setValue(OPTIONS[1].value)}>
Set {OPTIONS[1].label}
</Button>
</div>
</CunninghamProvider>
);
@@ -156,6 +162,12 @@ export const SearchableControlled = () => {
onChange={(e) => setValue(e.target.value as string)}
/>
<Button onClick={() => setValue("")}>Reset</Button>
<Button onClick={() => setValue(OPTIONS[0].value)}>
Set {OPTIONS[0].label}
</Button>
<Button onClick={() => setValue(OPTIONS[1].value)}>
Set {OPTIONS[1].label}
</Button>
</div>
</CunninghamProvider>
);