facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
229.83k stars 47.06k forks source link

[React 19] useState's setter function becomes arity 2 #31578

Open davesnx opened 1 week ago

davesnx commented 1 week ago

Summary

The setter from useState becomes arity 2 in React's 19.0.0-rc.1 release.

Haven't found a changelog entry (https://react.dev/blog/2024/04/25/react-19 or https://react.dev/blog/2024/04/25/react-19-upgrade-guide), neither looking at the commits, so I'm not sure if it's intentional or I might miss something here.

Demo

React's 18 setStates is arity 1 https://codesandbox.io/p/sandbox/setstate-arity-1-react-18-pfdr3h React's 19.0.0-rc.1 setState becomes arity 2 https://codesandbox.io/p/sandbox/setstate-arity-2-issue-react-19-x3l3kd

eps1lon commented 1 week ago

Thanks for reporting. This is due to automatic compiler optimizations. Why is this an issue?

davesnx commented 1 week ago

In the issue mentioned, there's a work in progress for reason-react support for React 19.

We use Melange to compile Reason to JavaScript and due the fact that all reason code is curried means that we need to annotate when a function (from the JavaScript world) is uncurried (almost all JavasScript functions are).

If they are annotated with uncurry, we call the code directly since we explicitly tell the compiler to do so.

If we don't, we wrap with a small utility that checks arity (fn.length) and applies their arguments accordingly.

Since the beginning, useState wasn't annotated because functions with arity 1 can't be curried (since they only have 1 argument). Now useState became arity 2, which implies we need to explicitly annotate it as uncurried and implies a breaking change.


This is the why, and I'm aware that's a long explanation that is a bit further from React.js per se, but I opened the issue to understand it better, and I wasn't aware of any automatic optimisations in the react core itself. Can you give me some pointers so that I could understand this better? Maybe there's a way to know what kind of optimisations?

eps1lon commented 1 week ago

So reason-react can work around this?

but I opened the issue to understand it better, and I wasn't aware of any automatic optimisations in the react core itself. Can you give me some pointers so that I could understand this better? Maybe there's a way to know what kind of optimisations?

We use Closure compiler. We used to only apply this to prod but now do to dev so Closure added another argument to avoid accessing arguments in https://github.com/facebook/react/blob/6f0dc2947bed21f9be484f37eb32d02fdc4c0481/packages/react-reconciler/src/ReactFiberHooks.js#L3755-L3761

davesnx commented 1 week ago

So reason-react can work around this?

I'm going to have a hard time with this. From what I can observe, It might differ from your reply:

The arity on the development version (using ESM.sh) is 1, while the arity of installation via npm (and bundled for prod) is 2.

It's worth mentioning that it's the same with the seducer dispatcher, is that expected as well?

davesnx commented 1 week ago

Does the compiler add this to avoid accessing arguments as a kind-of-a-linter mechanism for React core devs?

From the outside, it seems like a good tradeoff to use another mechanism for it and try not to "break" the arity.

eps1lon commented 1 week ago

We don't know why Closure does this. This specific optimization was removed in later versions but updating is not trivial: https://github.com/facebook/react/pull/31587

davesnx commented 1 week ago

Saw the PR. Thanks for the effort