Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.)

like image 841
M.S. Avatar asked Nov 16 '25 03:11

M.S.


2 Answers

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.

like image 125
CertainPerformance Avatar answered Nov 17 '25 20:11

CertainPerformance


Update::

[]
For your scenario (where you cannot keep creating new callbacks and passing them to your 3rd party library), you can use useRef to keep a mutable object with the current state. Like so:
[]
You can use the react-usestateref NPM package
<>
React hooks: accessing up-to-date state from within a callback \

  • (found this, after I wrote all that code...)
  • (though the author didnt encapsulate the ref (again, it deps on use case))
  • (so, to get the latest state, it really seems no other way)


An improved version for useStateRef

  • use a function setStateRef to set, instead of useEffect (Idk why Op use it)
  • allow state initializer function
  • encapsulate the access to ref (deps on your use case though)
  • typescript support
  • (ignore the comments if not needed)
  • (may not be the best though)
import React, { Dispatch, SetStateAction } from 'react';

/**
 * @purpose::
 * @topic: `state` can go into **`stale state closure problem`**
 * @know: the `state` is designed to be immutable in React -- every time you setState, you pass in a **new copy**
 * @pb: but when the `state` is in a **closure** (eg: `useEffect`), the reference of the state can be **stale**
 * @soln-not_desired: you need to use `dependency array` to update the `state` in `useEffect`
 * @soln-desired: you can use `ref` -- every time you `.current =`, the ref reference will still **stay the same**.
 * (-- @note: the value you pass in to the `.current` can still be a new copy -- but that wont matter)
 *
 * @main-res: https://stackoverflow.com/questions/66603920/combining-usestate-and-useref-in-reactjs
 * @dk still dk is this proper // Dont know if there is a better way.
 * @param initialState
 * @returns
 * state: use this to get the state & for rerender
 * getRefValue(): use this to get the state value  (-- with no `stale state closure problem`)
 * setStateRef(): use this to change state & ref (both sync)
 * - Dont use `ref.current` to change the ref (Only) -- to avoid that, `ref` is not returned (encapsulated), only getRefValue() is provided.
 * // ;encapsulate; @dk if this encapsulate is proper documentation
 * // ;encapsulate; export interface MutableRefObjectOnlyWhenInInternalCode<T> extends React.RefObject<T> { readonly current: T; }
 * // ;encapsulate; // cuz Java.. new Interface, but still, changing from mutable to immutable, feels against the syntax, and confusing.
 * // ;encapsulate; // Maybe should return another object instead?  // dont make copy, that defeat the whole purpose of Ref...
 * // ;encapsulate; // if only return getter for value, or return wrapper ... that if adds extra layer understanding ...
 * // ;encapsulate; // still, once have access, the use case is so unpredictable, not just a documentation can save the day .. better just completely encapsualte it -- cuz for now, dont see usage of accessing the Real Ref... // the Type, need to change code aga ..
 * // potential concurrency pb with Ref? ..
 * // actually the ref is refer to the new copy also... it just not stale
 */
export function useStateRef<S>(initialState: S | (() => S)): readonly [S, Dispatch<SetStateAction<S>>, () => S] {
  const [state, setState] = React.useState<S>(initialState);

  const ref: React.MutableRefObject<S> = React.useRef<S>(state); // dont just passin the initalState -- it can be a Function

  // @note,aga: https://stackoverflow.com/questions/45472944/how-to-define-the-type-of-a-named-function https://stackoverflow.com/questions/70053133/how-to-apply-a-function-type-to-a-function-declaration
  const setStateRef: Dispatch<SetStateAction<S>> = function (newState) {
    // #>> state initializer function
    if (typeof newState === 'function') {
      if (typeGuard_S2S<S>(newState)) {
        setState((prevState) => {
          const result = newState(prevState);
          ref.current = result;
          return result;
        });
      } else {
        throw new TypeError(`typeof newState is a function, but not in the structure of ((prevState: S) => S): ${newState}`);
      }
    }
    // #>> state
    else {
      ref.current = newState;
      setState(newState);
    }
  };

  /** (for encapsulation on ref) */
  function getRefValue(): S {
    return ref.current;
  }

  // ;encapsulate; return [state, setStateRef, ref as React.MutableRefObject/RefObject<S>] as const;
  return [state, setStateRef, getRefValue] as const;
}

/**
 * This is actually unsafe & incompleted.
 * There is only limited things you can test with type guard on functions.
 * // ;no_knowlres; aga, I dont know how React in source code checks the function type. (maybe they do the same -- just check for function type)
 * // ;no_knowlres; Im unable to find the source code in Vscode, idk cuz React doesnt publish the source code to npm or what.
 * // ;no_knowlres; Guding through Github source code instead is a pain.
 * @param funcTest
 * @returns
 */
function typeGuard_S2S<S>(funcTest: SetStateAction<S>): funcTest is (prevState: S) => S {
  if (typeof funcTest === 'function') {
    return true;
  }
  return false;
}
like image 21
Nor.Z Avatar answered Nov 17 '25 19:11

Nor.Z



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!