React Hook delayed useEffect firing?
Asked Answered
C

5

11

I am trying to use useEffect() in my react hook to update the state when my props change. but there is a delay and the useEffect is only firing after I click again on an element in my hook. Im fairly new to using hooks and any help would be appreciated. Thanks

function ImageOrderSelect (props) {
  const [clicked, setClicked] = useState(false)
  const [options, setOptions] = useState(props.options)
  const [current, setCurrent] = useState(props.current)

  useEffect(() => {
      setCurrent(props.current)
    }, [props.current])


  if(!clicked){
    return (
        <div className="image-order-select-icon" onClick={() => setClicked(!clicked)}>
            <FontAwesomeIcon size="lg" icon={faCircle} />
            <p>{current}</p>
        </div>
    )
  } else if(clicked){
      return (
          <div className="image-order-select">
              {optionsList}
          </div>
      )
  }


}
Caravel answered 4/9, 2019 at 12:46 Comment(2)
What do you mean by delay? You have passed props.current as dependency to useEffect hook, so it will execute automatically when it receives next props.current.Subtitle
It wouldn't update the hook state for current, even though the props were changing. but when i clicked again on another of my elements it would then update so it was as if it was 1 step behind all the time.Caravel
O
13
    useEffect(() => {

      setTimeout(()=>{
       setCurrent(props.current)
      }, 1000)
  
    }, [props.current])

You just need to add timeout/delay before displaying the current...

or Best approach add a callback function

useEffect(() => {

 
   setCurrent(() => props.current)
  

}, [props.current])
Overuse answered 7/1, 2020 at 4:18 Comment(0)
A
3

Im not exactly sure what desired effect you are going for, but here is a little cleaned up code of yours. Maybe that will help you further.

function ImageOrderSelect ({ current, options ) { // deconstruct here to save code
  const [clicked, setClicked] = useState(false);
  // you dont have to keep additional state since props is a state.
  useEffect(() => {
    setTimeout(() => {
      // do something here 1 sec after current has changed
    }, 1000);
  }, [current]);

  useEffect(() => {
    setTimeout(() => {
       // do something 1 sec after clicked has changed
    }, 1000);
  }, [clicked]);

  if(!clicked){
    return (
        <div className="image-order-select-icon" onClick={() => setClicked(!clicked)}>
            <FontAwesomeIcon size="lg" icon={faCircle} />
            <p>{current}</p>
        </div>
    )
  } else if(clicked){
      return (
          <div className="image-order-select">
              {optionsList}
          </div>
      )
  }
}

In case you want an effect triggered 1sec after the last click on your image, you have to reset the timeout on every click untill it expires. This is common practice for any delayed effect on user interactions:

let timeout;

useEffect(() => {
  clearTimeout(timeout);
  timeout = setTimeout(() => {
     // do something 1 sec after a click is done
     // but dont do anything if another click happened before 1sec of the previous 
     // click has expired
  }, 1000);
}, [clicked]);

I can definetlely help you if you could give a more detailed code example and more in depth explanation of your desired UI effect.

Aeriform answered 3/7, 2020 at 6:18 Comment(1)
well done for cleaning up OP's codeTain
P
1

If the component is unloaded by a user action like moving to another window. The timeout examples here will still execute the action. Depending on the case it would produce bugs. Here is an example of how to remove the timer on unload.

  const [timerReference, setTimerReference] = useState(undefined);

  useEffect(() => {
    setTimerReference(setTimeout(() => {
      handler();
    },2000));
    return () => {
      clearTimeout(timerReference);
    }
  }, []);
Ponderable answered 19/10, 2022 at 17:5 Comment(0)
C
0
  let optionsList = options.map((option, index) => {
  return (
  <div key={index} onClick={() => {

      props.handleImageSort(option, props.index)
      setTimeout(() => {
          setClicked(!clicked)
      }, .500)
  }}>
    <p>{option}</p>
  </div>
  )

})

The issue was I was trying to call setClicked at the same time which for some reason was stopping it from calling. the setTimeout makes it work although not ideal. if anyone has a better solution would be interested to hear it.

Caravel answered 4/9, 2019 at 13:1 Comment(2)
I think its wrong logic. You probably need to watch clicked in your useEffect.Clothesline
Welcome to StackOverflow, Robert. Would you add your answer to the question, as it's what you've attempted and requesting for a better answer?Lampion
V
0
const timeout = useRef<NodeJS.Timeout>();
  useEffect(() => {
    clearTimeout(timeout.current);
    timeout.current = setTimeout(() => {
      "Your action here"
    });
  }, [your dependencies here]);
Viral answered 17/4, 2024 at 23:56 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.