0
votes

So I have this bit of code that does not work as expected. Current focus has been set using useState() on the parent component, hence its a state variable. However, when the currentFocus value changes in the parent, focus variable here itself is not updated. I would have expected the re render of the parent component, which in turn rerenders this component would cause the foucs value to change.

import React, { useRef, useEffect, useState } from 'react';
const CookieDetails = props => {
  const {
    name,
    cost,
    value,
    numOwned,
    onMouseClick,
    cookieId,
    currentFocus,
  } = props;

  let cookieInfoRef = useRef(null);
  //My focus doesnt change even if I change currentFocus in parent component
  const [focus, setFocus] = useState(currentFocus);

  useEffect(() => {
    console.log('currentFocus', currentFocus);
    console.log('focus', focus);
    console.log('cookieID', cookieId); 
    if (cookieInfoRef.current && cookieId === focus) {
      console.log('current', cookieInfoRef.current);
      cookieInfoRef.current.focus();
    }
  }, [focus]);

  return (
    <React.Fragment>
      <button
        onClick={onMouseClick}
        ref={cookieId === focus ? cookieInfoRef : null}
      >
        <h3>{name}</h3>
        <p>Cost:{cost}</p>
        <p>Value:{value}</p>
        <p>Owned:{numOwned}</p>
      </button>
    </React.Fragment>
  );
};

export default CookieDetails;

Now I can solve this problem by doing the following instead,

import React, { useRef, useEffect, useState } from 'react';
const CookieDetails = props => {
  const {
    name,
    cost,
    value,
    numOwned,
    onMouseClick,
    cookieId,
    currentFocus,
  } = props;

  let cookieInfoRef = useRef(null);

  useEffect(() => {
    console.log('currentFocus', currentFocus);
    console.log('cookieID', cookieId);        
    if (cookieInfoRef.current && cookieId === currentFocus) {
      console.log('current', cookieInfoRef.current);
      cookieInfoRef.current.focus();
    }
  });

  return (
    <React.Fragment>
      <button
        onClick={onMouseClick}
        ref={cookieId === currentFocus ? cookieInfoRef : null}
      >
        <h3>{name}</h3>
        <p>Cost:{cost}</p>
        <p>Value:{value}</p>
        <p>Owned:{numOwned}</p>
      </button>
    </React.Fragment>
  );
};

export default CookieDetails;

But I only wanted to run the useEffect hook when focus/currentFocus is updated and not after every render. So why does this happen? What am I am missing to understand here.

Also I previously noticed if you did something like this, the handleStuff function always uses the initial value of 100 (so its always 100 + 10) instead of incrementing by 10 each time a key is pressed. I can solve this by removing the empty [] as the second argument in useEffect. However, I expected the handleStuff to still update with the latest value instead, given that the eventListner has already been added on initial render and on second keydown it should add 10 to 110 instead of 100, but it keeps using the initial state value of 100 instead. Why must you clear the old listener and add the new one each time for it work?

[value, setValue] = useState(100)

handleStuff = (event) => {
 setValue(value+10)
}

useEffect(() => {
  window.addEventListener('keydown', handleStuff)
  return () => {
    window.removeEventListener('keydown', handleStuff)
  }
 },[]);

I am not really insterested in the solutions, I am really insterested in understanding how useEffect() and useState() hooks function in this conditions.

1
We remove the event listener each time because otherwise you’ll have many listeners instead of one.evolutionxbox
Try understanding the functions without focus first as I think that is over complicating thingsevolutionxbox
@evolutionxbox why would there be multiple listeners, bu using [] as the second argument, I am only adding one event listener. The problem seems to be that event listener however always uses that very first initial state with which it was rendered. I don't think using a simpler example would help here. As I am particularly interested in this cases. I usually dont have a problem using the hooks, I already provided the solutions to this problems. It's the explanation of this certain behavior that I am looking forssaquif
The problem seems to be that event listener however always uses that very first initial state with which it was rendered this is called a closureThomas
It's some Initial Value. If it would behave the way you expect, the useState hook would be useless. It would produce this: const value = initialValue; And setValue would be pointless because by the next call, value would be reset to the initial value.Thomas

1 Answers

6
votes

Hey the useEffect hook can be a bit confusing. But the way to think of it is

useEffect(() => {
  // Called every render
})

useEffect(() => {
  // Called on first render
}, [])

useEffect(() => {
  // Called when x y & z updates
}, [x, y, z])

The problem is you aren't listening for the currentFocus update in your code.

useEffect(() => {
  // Called when focus updates
}, [focus]);

useEffect(() => {
  // Called when props.currentFocus & focus updates
}, [props.currentFocus, focus]);

Now to explain your confusion:

// My focus doesnt change even if I change currentFocus in parent component
const [focus, setFocus] = useState(currentFocus);

When you are passing in currentFocus here the only time the currentFocus value is being passed to the focus useState is on the first render. So you need to listen for the currentFocus update and then set the new focus which should work how you want!

// Focus set to props.currentFocus on first render
const [focus, setFocus] = useState(currentFocus);

useEffect(() => {
  // Update focus
  setFocus(props.currentFocus)
}, [props.currentFocus])

useEffect(() => {
  // currentFocus will be the same as focus now
  console.log('currentFocus', currentFocus);
  console.log('focus', focus);
  console.log('cookieID', cookieId); 
  if (cookieInfoRef.current && cookieId === focus) {
    console.log('current', cookieInfoRef.current);
    cookieInfoRef.current.focus();
  }
}, [focus]);

Hope that helps!