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.)
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.
Update::
[]
For your scenario (where you cannot keep creating new callbacks and passing them to your 3rd party library), you can useuseRefto keep a mutable object with the current state. Like so:
[]
You can use thereact-usestaterefNPM package
<>
React hooks: accessing up-to-date state from within a callback \
ref (again, it deps on use case))An improved version for useStateRef
setStateRef to set, instead of useEffect (Idk why Op use it)state initializer functionref (deps on your use case 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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With