tailwindlabs / headlessui

Completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.
https://headlessui.com
MIT License
25.81k stars 1.07k forks source link

Combobox v2 issue with dynamic fetched options in nextjs #3184

Closed lasseklovstad closed 4 months ago

lasseklovstad commented 4 months ago

What package within Headless UI are you using?

@headlessui/react

What version of that package are you using?

v2.0.3

What browser are you using?

Chrome

Reproduction URL

Reproduction CodeSandbox

Describe your issue combox-issue

After selecting an option and changing the input value the Input value resets back to the selected option. This is not how it was in the latest v1. This issue also seems only to occure in Nextjs. I could not reproduce it with the vite issue template.

  1. Type the letter "T" in inputfield and select the "Tailwind" option
  2. After selecting we want to search for something new, so we backspace 4 letters.
  3. This will trigger react query to run again and the input is reset to the selected value.

The excpected behavior is to not change the displayValue when changing the inputValue.

juliancoleman commented 4 months ago

Here to say I'm experiencing the same thing

juliancoleman commented 4 months ago

@lasseklovstad Following up here: I can temporarily get around this by setting the Combobox value to null if the input value has changed. This obviously isn't great and impacts the UX negatively (e.g. blurring the field will not reset to the previously selected value by default), which means more code will need to be written by you to restore the ideal UX.

Here's some (simplified) code I'm using that's running in production now using @headlessui/react ^2.0.3

type Place = {
  code: string; // can be empty string
  type: string; // can be empty string
};

export type LocationPickerProps = {
  /**
   * Available options to populate the menu
   */
  options: LocationResponse;
  /**
   * The selected value
   */
  value: Place | null;
  /**
   * Callback whenever a new selection is made
   */
  onChange(value: Place | null): void;
  /**
   * Callback whenever the search input value changes
   */
  onSearch(search: string): void;
};

export function LocationPicker(props: LocationPickerProps) {
  const {
    onChange,
    onSearch,
    options,
    value,
  } = props;

  const handleSearch = useMemo(
    () => debounce((input: string) => onSearch(input)),
    [onSearch],
  );

return (
    <Combobox
      value={value}
      onChange={(value) => onChange(value)}
      as="div"
      className="w-full"
      by="code"
      immediate
    >
      <Label className="flex w-full items-center gap-2.5 rounded-lg border border-grey-200 bg-grey-50 px-3 py-2.5 transition-all focus-within:border-grey-300 hover:border-grey-400">
        <ComboboxInput
          autoComplete="off"
          placeholder="Choose location"
          onChange={({ target }) => {
            onChange(null); // <-- this fixes the bug in question
            handleSearch(target.value);
          }}
          displayValue={(location: Place | null) => {
            if (
              location == null ||
              (location.type === "" && location.code === "")
            ) {
              return "";
            }

            const found = options.find(
              (o) =>
                o.code === location.code &&
                o.granularLocationType === location.type,
            );

            return found?.title ?? "";
          }}
          className="flex-1 bg-transparent text-sm outline-none"
        />
      </Label>

      {/* ... */}

    </Combobox>
  );
}
RobinMalfait commented 4 months ago

This should be fixed by #3259, and will be available in the next release.

You can already try it using:

juliancoleman commented 3 months ago

Thanks @RobinMalfait 👍