Reputation: 330
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.
Upvotes: 1
Views: 1695
Reputation: 1606
Hey the useEffect hook can be a bit confusing. But the way to think of it is
useEffect(() => {
// Called in an infinite loop (most cases you won't need this)
})
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!
Upvotes: 6