Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useRef to store previous state value

I am confused about the below usage of useRef to store the previous state value. Essentially, how is it able to display the previous value correctly. Since the useEffect has a dependency on "value", my understanding was that each time "value" changes (i.e. when user updates textbox), it would update "prevValue.current" to the newly typed value.

But this is not what seems to be happening. What is the sequence of steps in this case?

function App() {
  const [value, setValue] =  useState("");
  const prevValue = useRef('')
  useEffect(() => {
    prevValue.current = value;
  }, [value]);
  return (
    <div>
      <input
        value={value}
        onChange={e => setValue(e.target.value)}
      />
      <div>
        Curr Value: {value}
      </div>
      <div>
        Prev Value: {prevValue.current}
      </div>
    </div>
  );
}
like image 640
copenndthagen Avatar asked Jan 19 '21 04:01

copenndthagen


People also ask

How do I use useRef to store values?

Storing element references with useRef To do this, create the ref, and then pass it into the element: const Component = () => { const ref = useRef(null); return <div ref={ref}> Hello world </div>; }; With this reference, you can do lots of useful things like: Grabbing an element's height and width.

How do you find the previous state value in a React functional component?

With React class components you have the componentDidUpdate method which receives previous props and state as arguments or you can update an instance variable (this. previous = value) and reference it later to get the previous value.

Can I use useRef instead of useState?

If you want to update data and cause a UI update, useState is your Hook. If you need some kind of data container throughout the component's lifecycle without causing render cycles on mutating your variable, then useRef is your solution.

How to get the current value of a useref?

To get the latest value from all code positions (render, useEffect body, disposal function body), you have to use at least two useRef s. export const usePrevRef = value => { const currentRef = useRef () const prevPref = useRef () prevPref.current = currentRef.current currentRef.current = value return prevPref }

How to make useref always store the latest value?

You can't actually say useRef always store the latest value, it actually still store the regular value, except it won't trigger re-render, since the reference to this ref is fixed after initialization (only ref.current is changing). Another approach is change ref value after if condition.

How to update the value of the ref object in useref?

With the useRef Hook, the value saved in the ref object is kept the same across re-renders. The value is neither lost nor recomputed; it remains the same. It’s worth mentioning that the only way to update the ref object is to directly set the value of the current property, i.e., specialVariable.current = "NEW_SPECIAL_VARIABLE.

How can I show the previous value in the Ui?

But if you want to show the “previous” value in the UI anywhere, a much more reliable approach here would be for the hook to return the actual previous value. Let’s implement exactly that. In order to do that, we just need to save in ref both values - previous and current.


3 Answers

Ok so while this technically works, it's a confusing way of doing it and may lead to bugs as you add more stuff. The reason it works is because useEffect runs after state changes, and changing ref values don't cause a re-render. A better way would be to update the ref value during the onChange handler, rather than in the effect. But the way the code you posted works is as follows:

  1. Initially, both would be empty
  2. User types something, triggering a state change via setValue
  3. This triggers a re-render, so {value} is the new value, but since the ref hasn't been updated yet, {prevValue.current} still renders as the old value
  4. Next, after the render, the effect runs, since it has value as a dependency. So this effect updates the ref to contain the CURRENT state value
  5. However, since changing a ref value doesn't trigger a re-render, the new value isn't reflected in what's rendered

So once those steps above finish, then yes technically the state value and the ref are the same value. However, since the ref change didn't trigger a re-render, it still shows the old ref value in what's rendered.

This is obviously not great, cos if something else triggers a re-render, like say you had another input with a connected state value, then yes the {prevValue.current} would then re-render as the current {value} and then technically be wrong cos it would show the current value, not previous value.

So while it technically works in this use case, it'll be prone to bugs as you add more code, and is confusing to wrap the head around

like image 114
Jayce444 Avatar answered Oct 20 '22 03:10

Jayce444


useRef() is used to persist values in successive renders. If you want to keep the past value put it in the onChange:

<input
    value={value}
    onChange={e => {
       prevValue.current = value;
       setValue(e.target.value)
    }}
   />

This will assign it to the current state value of value before it is changed and you will not need the useEffect hook.

like image 42
marsh Avatar answered Oct 20 '22 05:10

marsh


https://reactjs.org/docs/hooks-reference.html#useref useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

https://reactjs.org/docs/hooks-effect.html Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update

So it happens in sequence steps:

1 - Input change (example: "1")
2 - Component re-render
3 - useEffect run and set value ("1") to prevValue.current. This does not make component re-render. At this time prevValue.current is "1".
4 - Input change (example: "12")
5 - Component re-render => show prevValue.current was set before in step 3 ("1")
6 - useEffect run and set value ("12") to prevValue.current. This does not make component re-render. At this time prevValue.current is "12".
... 
like image 2
Thanh Avatar answered Oct 20 '22 05:10

Thanh