Search code examples
reactjsreact-hooksuse-state

Combining useState and useRef in ReactJS


I have some variables of which I need the reference as well as the state. I found something here, that helped me: https://stackoverflow.com/a/58349377/7977410

Pretty much it just kept two variables synchronized (in pseudo code):

const [track, setTrack] = useState('INIT');
const trackRef = useRef('INIT');

// Whenever changing, just both are updated
setTrack('newValue');
trackRef.current = 'newValue';

I was wondering if it was beneficial to combine those two into a new hook. Something like this:

const useStateRef(initialState: any) {
  const [state, setState] = useState<typeof initialState>(initialState);
  const ref = useRef<typeof initialState>(initialState);

  useEffect(() => {
    setState(ref);
  }, [ref]);

  return [state, ref];
}

What would be the best way to do this? Is it even critical to do something like this?

(The background is, I have some self-repeating functions, that need the reference to change what they are doing. But I also need the state variable to rerender visible changes when the same variables are changing... Maybe there is a completely different way to do this, but I am still curious about this approach.)


Solution

  • It's possible, but to make the typings proper, you should use generics instead of any, and the effect hook needs to be reversed - change the ref.current when the state changes. You'll also want to return the state setter in order to change the value in the consumer of useStateRef.

    const useStateRef = <T extends unknown>(initialState: T) => {
      const [state, setState] = useState(initialState);
      const ref = useRef(initialState);
    
      useEffect(() => {
        ref.current = state;
      }, [state]);
    
      // Use "as const" below so the returned array is a proper tuple
      return [state, setState, ref] as const;
    };
    

    Or, to update synchronously, remove the effect hook:

    if (ref.current !== state) ref.current = state;
    

    Also note that there should never be any need to have a ref that only ever contains a primitive. const trackRef = useRef('INIT'); can be refactored away entirely and replaced with track. Refs are generally useful when dealing with objects, like HTMLElements.