react-select update value when mutating api data

Viewed 760

i have a react-select component in which i am filing options from api data, whenever i am editing name of a person from api, the names in the options change but the name on the value remains same.

i want to change the value of select after i change the name of a person.

--here i am updating the name of a person using react-query

  const deliveryPersonUpdate = (values) => {
    const id = deliveryBoy && deliveryBoy.id;
    const params = { deliveryBoyId: id, ...values };
    updateDeliveryPerson.mutate(params, {
      onSuccess: (data) => {
        toggle();
        setDeliveryBoy(data?.delivery_boy);
        queryClient.invalidateQueries([GET_DELIVERY_BOY_LIST.name]);
        toast.success("Category Updated");
      },
      onError: (error) => {
        showErrorMessages({ error });
      },
    });
  };

  const handleSubmit = (values) => {
    deliveryPersonUpdate(values);
  };

--select component

<Select
 onChange={(obj) => handleChange(obj)}
                paintBg
                noOptionsMessage={noOptionsMessage}
                name="deliveryBoySelect"
                className={cx(styles.addDeliveryBoySelect)}
                defaultValue={
                  !isEmpty(data?.delivery_boys) && {
                    label: data?.delivery_boys?.[0].name,
                    value: data?.delivery_boys?.[0].id,
                  }
                }
                options={data?.delivery_boys.map((d) => ({
                  label: d.name,
                  value: d.id,
                }))}
                isSearchable
              />

--handle change for select

const handleChange = (e) => {
    const deliveryBoyData = data.delivery_boys.find(
      (obj) => obj.id === e.value
    );
    setDeliveryBoy(deliveryBoyData);
  };

--here, after changing name of person, options are updating right away but the value is not updating enter image description here

1 Answers

The problem you are facing is that you are using defaultValue for the select, which means the select is uncontrolled. Any time the defaultValue is changed, the select doesn't react to this change because it's not supposed to, it's just the default value.

You have two options:

  1. make the select controlled (by using state)
  2. make the select keyed (by re-mounting it when default value changes)

As for controlled select

You would have to replace defaultValue by value but also attach an onChange handler that changes the state. You kind of already have the onChange as it updates the state in RQ, but since you are using defaultValue, it doesn't propagate back. However, if you just used value I think there would be blinking because RQ is async by nature, so the user could see a frame where the value is still out of sync. So in order to fully do this, you would have to introduce a sync state as well.

const [value, setValue] = useState(props.value)
const onChange = (e) => {
  setValue(e.target.value)
  props.onChange(e.target.value)
}
useEffect(() => {
  setValue(props.value)
}, [props.value])

<select value={value} onChange={onChange} />

What this does it that it keep a local state using setState, handles update from props using useEffect and uses value instead of defaultValue thanks to that. You could also lift the state up if necessary.

As for keyed component

In case the previous solution is not ergonomic or not good for any reason and the component is small-ish, you can also decide that instead of keeping the local state, you will just unmount the component and mount a fresh instance. What the result is, is that when the value changes, it mounts a new component so it can use defaultValue again. You keep the select unchanged except for adding a key prop.

<select key={props.value} defaultValue={props.value} onChange={onChange}>

Having the key the same as the value means that they will be in sync. When the local value changes, it doesn't blink, because we do not set value, the DOM updates on it's own and when the props.value finally changes is async manner by RQ, it create the component anew, making it with the current defaultValue.

Related