Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Provide an optional ref in useState to combat stale state issues #22998

Closed
jasontlouro opened this issue Dec 20, 2021 · 4 comments
Labels
React 18 Bug reports, questions, and general feedback about React 18 Type: Discussion

Comments

@jasontlouro
Copy link

jasontlouro commented Dec 20, 2021

In React, there is a very common issue of needing the most up-to-date version of state from inside a useEffect, timeout, callback, asynchronous function, etc. It seems the easiest solution is to use a ref, as encouraged by React's own documentation here:

If you intentionally want to read the latest state from some asynchronous callback, you could keep it in a ref, mutate it, and read from it.

Typically, the pattern I use for this is as follows:

const [value, setValue] = useState('some-initial-value');
const valueRef = useRef(value);
valueRef.current = value;

This ensure that on every render we update the ref to contain the latest version of the value state. This works fine, but it's messy - 3 lines of code instead of 1 for every state we need the latest version of. For components with a lot of useState calls this gets really ugly really fast for something that's remarkably simple and doesn't affect performance.

My feature request is this: can we just get a ref that points back to the most recent value of state as part of the useState call? I'd love to be able to do this:

const [value, setValue, valueRef] = useState('some-initial-value');

And then just reference valueRef.current from within effects, timeouts, intervals, event listener functions, etc. The amount of times I have to use the messy ref workaround is frustrating, and this looks like something React would be able to do so easily just by sticking the latest value in the ref on every render. People could then use it if they want it or continue to call useState() as normal otherwise.

Would certainly clean up my company's codebase markedly. Are there any potential downsides to providing this optional ref that I am missing?

@jasontlouro jasontlouro added React 18 Bug reports, questions, and general feedback about React 18 Type: Discussion labels Dec 20, 2021
@jasontlouro jasontlouro changed the title Feature Request: Provide an option ref in useState Feature Request: Provide an optional ref in useState Dec 20, 2021
@jasontlouro jasontlouro changed the title Feature Request: Provide an optional ref in useState Feature Request: Provide an optional ref in useState to combat stale state issues Dec 20, 2021
@incepter
Copy link

If I understand well, you are willing to read the state value from an asynchronous function's callback. Although this is a common pattern, I don't believe it is a right one.

One easy solution to avoid this and stick to the react's mental model is to update the state, and do an effect (useEffect) on the state value then you can perform what the callback is supposed to do.

On the other hand, you can extract this small abstraction to a hook that should have a ref value in sync with the last state's value.

One last thing, you only need this if you have concurrency: multiple async functions that may have a pending state at the same time.
Here; I see two options:

  1. your callback should close over its state value and should not allow dirty updates (a later call answers before the former).
  2. The later call should abort the previous.

I recommend reading this blog post about designing asynchronous callback in react.

@vkurchatkin
Copy link

vkurchatkin commented Dec 20, 2021

There are several issues to address here.

First of all, nothing stops you from implementing a custom hook that does what you want. This doesn't need to be a part of React.

Second, the exact code you specified has a serious problem:

const [value, setValue] = useState('some-initial-value');
const valueRef = useRef(value);
valueRef.current = value; // can't do this, refs shouldn't be mutated during render

Instead, refs should be mutated in effects. So, your custom hook might look like this:

function useRefState(initialState) {
  const [state, setState] = useState(initialState);
  const ref = useRef(initialState);
  
  useEffect(() => {
    ref.current = state;
  });

  return [state, setState, ref];
}

And the last thing to remember, that there is really no concept of "current" or "most up-to-date version of state", because React can have multiple concurrent renders going on with different versions of state. The closest thing you have is "state that belongs to most recently committed render".

@bvaughn
Copy link
Contributor

bvaughn commented Dec 20, 2021

Variations of this request have been proposed several times:

At least within our current framework, this type of proposal should go through our RFC process:
https://github.com/reactjs/rfcs#react-rfcs

I suggest adding your input on the existing RFC:
reactjs/rfcs#201

I'm going to close this issue as a duplicate.

@bvaughn bvaughn closed this as completed Dec 20, 2021
@salazarm
Copy link
Contributor

Mutating and then using refs in render to gate logic isn't safe in concurrent mode. refs will get reset if the tree the component is in suspends or in offscreen mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
React 18 Bug reports, questions, and general feedback about React 18 Type: Discussion
Projects
None yet
Development

No branches or pull requests

5 participants