September 3, 2019
4 min read
I ran into a situation where I needed to use the value state of a useState
hook inside an event handler callback. The callback would execute later on after an event was triggered. By this time, the value of my state would have changed. This is where my trouble began.
I was passing a simple constant variable into the callback that was not a React component. This callback was unaware that my state value had been changed.
const ComponentWithEvent = () => { const [state, setState] = useState(0);
useEffect(() => { setTimeout(() => { console.log(state); // this will log 0 }, 100); }, []);
setState(1);};
useRef
allows us to create an object that contains a reference to any variable on the ref's current
property. This value will be persisted across React re-renders. We'll pass this ref to our callback now instead of the value itself.
Now our callback has a reference that we can update from React, but we need code to actually do the updating!
const ComponentWithEvent = () => { const [state, setState] = useState(0); const stateRef = useRef(state);
useEffect(() => { setTimeout(() => { console.log(stateRef.curent); // this will still log 0 :-( }, 100); }, []);
setState(1);};
useEffect
will let us run code when the variables being watched by the useEffect hook are altered. In this case, we will watch the state
variable being created by our original useState
hook.
Now, every time that our state
variable changes we will update our stateRef
to keep the value referenced by our callback up to date.
const ComponentWithEvent = () => { const [state, setState] = useState(0); const stateRef = useRef(state); useEffect( () => { stateRef.current = state; }, [state], );
useEffect(() => { setTimeout(() => { console.log(stateRef.curent); // this will still log 1!!! }, 100); }, []);
setState(1);};
Putting this all together, we end up with a custom useRefState
hook! This will allow us to reuse this logic anywhere we need it in the future!
const useRefState = (initialValue) => { const [state, setState] = useState(initialValue); const stateRef = useRef(state); useEffect( () => { stateRef.current = state; }, [state], ); return [stateRef, setState];};
const ComponentWithEvent = () => { const [stateRef, setState] = useRefState(0);
useEffect(() => { setTimeout(() => { console.log(stateRef.curent); }, 100); }, []);
setState(1);};
My original useRefState
hook implementation, actually extended the setState
function of useState
to keep the stateRef up to date. However, I found an excellent post by Sébastien Castiel that used this much more elegant useEffect
approach.