react-nativenext.js

useState keeping the same value even after modifying in react


I have a nextjs application that uses shadcn-ui combobox component and there is a value that should return back to an empty string after modifying it with useState, but it does not. I do not understand why. Here is the code:

    'use client';

import { Button } from '@/components/ui/button';
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from '@/components/ui/command';
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@/components/ui/popover';
import { cn } from '@/app/lib/utils';
import { ChevronsUpDown, Check } from 'lucide-react';
import { CrossCircledIcon } from '@radix-ui/react-icons';
import * as React from 'react';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';

type ComboboxProps = {
  options: string[];
  searchPlaceholder?: string;
  paramsUpdate: string;
  height_button?: string;
  width_popover?: string;
};

export function Combobox({
  options,
  searchPlaceholder = 'Search items...',
  paramsUpdate,
  height_button,
  width_popover,
}: ComboboxProps) {
  const [open, setOpen] = React.useState<boolean>(false);
  const searchParams = useSearchParams();
  const [selectedOption, setSelectedOption] = React.useState<string>(
    searchParams.get(paramsUpdate) || '',
  );

  const { replace } = useRouter();

  const pathname = usePathname();

  const updateURLParams = (q: string) => {
    const params = new URLSearchParams(searchParams);
    params.set('page', '1');

    if (q) {
      params.set(paramsUpdate, q);
    } else {
      params.delete(paramsUpdate);
    }

    replace(`${pathname}?${params.toString()}`);
  };

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger className="flex" asChild>
        <Button
          variant="outline"
          role="combobox"
          aria-expanded={open}
          className={cn(
            'flex-none items-center justify-between text-wrap',
            height_button,
          )}
        >
          {selectedOption
            ? options.find((option) => option === selectedOption)
            : searchPlaceholder}
          <div className="flex space-x-2">
            <CrossCircledIcon
              className="ml-2 h-4 w-4 shrink-0 opacity-50 hover:opacity-100"
              onClick={() => {
                setSelectedOption('');
                updateURLParams('');
              }}
            />
            <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
          </div>
        </Button>
      </PopoverTrigger>
      <PopoverContent className={cn('p-0', width_popover)}>
        <Command>
          <CommandInput placeholder="Search ..." />
          <CommandList>
            <CommandEmpty>Not found</CommandEmpty>
            <CommandGroup>
              {options.map((opt, i) => (
                <CommandItem
                  key={opt}
                  value={opt}
                  onSelect={(value) => {
                    setSelectedOption(value === selectedOption ? '' : value);
                    console.log(value);
                    updateURLParams(value);
                    setOpen(false);
                  }}
                  className={cn(
                    i !== options.length - 1 && 'border-b border-gray-200',
                  )}
                >
                  <Check
                    className={cn(
                      'mr-2 h-4 w-4',
                      selectedOption === opt ? 'opacity-100' : 'opacity-0',
                    )}
                  />
                  {opt}
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
}

The most important part is this:

            <CommandItem
              key={opt}
              value={opt}
              onSelect={(value) => {
                setSelectedOption(value === selectedOption ? '' : value);
                console.log(value);
                updateURLParams(value);
                setOpen(false);
              }}

In this case value does not change to empty string when I click second time in the same item, I can see that the ui changes to the searchPlaceholder but console.log(value) still gives me the previous value, why ?


Solution

  • You need to set it to an empty string instead of the latest value which is the same as the last one

    onSelect={(value) => {
        let newVal = value === selectedOption ? '' : value;
        setSelectedOption(newVal);
        console.log(newVal);
        updateURLParams(newVal);
        setOpen(false);
    }}