Search code examples
reactjstypescriptreact-hooksreact-three-fiber

react function running twice thrice | ts


Here there everyone. I've a dropdown component in which I'm passing options and on clicking of those options I'm importing 3d models with R3F. so its working fine but the only catch is when the page loads for the first time first 2 or 4 clicks import totally random models ones that I dont indent to import but its happening. below is the code please take a look what's wrong with it.

import {
  useState,
  useEffect,
  useRef,
  Dispatch,
  SetStateAction,
} from "react";
const useOnClickOutside = <T extends HTMLElement>(
  ref: React.RefObject<T>,
  handler: () => void
) => {
  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (ref.current && !ref.current.contains(event.target as Node)) {
        handler();
      }
    };

    document.addEventListener("mousedown", handleClickOutside);

    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [ref, handler]);
};

interface Option {
  name: string;
  value: string | number;
  image: string;
  id: string;
}

interface DropdownProps {
  options: Option[];
  title: string;
  xAxis: number;
  yAxis: number;
  zAxis: number;
  SysmexArray: number[][];
  setSysmexArray: Dispatch<SetStateAction<number[][]>>;
  cr10_no_7thArray: number[][];
  setCr10_no_7thArray: Dispatch<SetStateAction<number[][]>>;
  ID: string;
  setID: Dispatch<SetStateAction<string>>;
}

const Dropdown: React.FC<DropdownProps> = (props) => {
  const [isOpen, setIsOpen] = useState(false);
  const [selectedOption, setSelectedOption] = useState<Option | undefined>(
    undefined
  );
  const dropdownRef = useRef<HTMLDivElement>(null);

  const toggleDropdown = () => setIsOpen(!isOpen);

  useOnClickOutside(dropdownRef, () => setIsOpen(false));

  useEffect(() => {
    const handleEscapeKeyPress = (event: KeyboardEvent) => {
      if (event.key === "Escape") {
        setIsOpen(false);
      }
    };
    
    document.addEventListener("keydown", handleEscapeKeyPress);

    return () => {
      document.removeEventListener("keydown", handleEscapeKeyPress);
    };
  }, []);


  /****************************************/
/*THIS FUNCTION IS NOT WORKING PROPERLY */
/**************************************** */
  const handleOptionClick = (option: Option) => {
    setSelectedOption(option);
    setIsOpen(false);
    if (selectedOption?.name === "m-st") {
      props.setID(() =>{

        
        props.setSysmexArray(() => [
          ...props.SysmexArray,
          [props.xAxis, props.yAxis, props.zAxis],
        ]);
      
        return"m-st"});
      }
    if (selectedOption?.name === "CR10") {
      props.setID(() => {
        
        props.setCr10_no_7thArray(() => [
          ...props.cr10_no_7thArray,
          [props.xAxis, props.yAxis, props.zAxis],
        ]);
       
        return"r-cr10"});
    }
  };

  return (
    <div className="relative pl-0" ref={dropdownRef}>
      <button
        type="button"
        className="flex items-center justify-between w-full px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
        onClick={toggleDropdown}
      >
        {selectedOption ? (
          <>
            {props.title}
            {console.log(`${selectedOption.name} : ${selectedOption.value}`)}
          </>
        ) : (
          props.title
        )}
        <svg
          className={`w-5 h-5 ml-2 transition-transform ${
            isOpen ? "transform rotate-180" : ""
          }`}
          viewBox="0 0 20 20"
          fill="currentColor"
          aria-hidden="true"
        >
          <path
            fillRule="evenodd"
            d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
            clipRule="evenodd"
          />
        </svg>
      </button>
      {isOpen && (
        <div className="absolute z-10 w-full mt-2 bg-white rounded-md shadow-lg">
          <ul className="py-1">
            {props.options.map((option) => (
              <li
                key={option.value}
                className="flex flex-col justify-center items-center px-4 py-2 text-sm text-gray-700 cursor-pointer hover:bg-gray-100"
                onClick={() => handleOptionClick(option)}
              >
                <img
                  src={option.image}
                  height={100}
                  width={100}
                  alt={option.name}
                />{" "}
             
                <div className="text-center">{option.name}</div>
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
};

export default Dropdown;

I've tried changing dependecy arrrays of useEffect, using useCallback to make sure it fire only what is got clicked but neither of them worked for me.


Solution

  • Use option argument instead of selectedOption state otherwise your function will use previous value of selectedOption. Also we don't know what setID function exactly does so this is a shot in dark. But try below and see if it works.

    const handleOptionClick = (option: Option) => {
        setSelectedOption(option);
        setIsOpen(false);
        if (option?.name === "m-st") {
          props.setID(() =>{
    
            
            props.setSysmexArray(() => [
              ...props.SysmexArray,
              [props.xAxis, props.yAxis, props.zAxis],
            ]);
          
            return"m-st"});
          }
        if (option?.name === "CR10") {
          props.setID(() => {
            
            props.setCr10_no_7thArray(() => [
              ...props.cr10_no_7thArray,
              [props.xAxis, props.yAxis, props.zAxis],
            ]);
           
            return"r-cr10"});
        }
      };