[React 19] ForwardRef props are not referentially stable, breaking downstream memoizations
Summary
I don't know if this is a known behaviour or a bug, but something that's worth highlighting at least.
ForwardRef components are not deprecated, but they're not perfectly backwards compatible either.
The mere existance of ref prop on a ForwardRef component, even if undefined, makes the component props referentially unstable, breaking a whole host of downstream memoizations.
Reproduction: https://codesandbox.io/p/sandbox/youthful-faraday-8m98cd?file=%2Fsrc%2FApp.tsx%3A34%2C23
React 18: https://codesandbox.io/p/sandbox/youthful-faraday-forked-32lrwm?workspaceId=8a8eeabe-fead-479a-a993-25a9868e8015
If it's a known behaviour – please highlight this in the docs. Don't think we would have gone ahead with upgrading yet until some of our dependencies would be supporting React 19, because a whole host of packages (charting, datagrids, etc.) that are performance sensitive, suddenly had performance issues.
If not, it needs some attention, as it has pretty bad performance implications.
I believe a lot of maintainers are not dropping ForwardRef yet, because they think it's perfectly backwards compatible, and it's easier to maintain backwards compatibility for them.
Was this working in React 18?
Absolutely: https://codesandbox.io/p/sandbox/youthful-faraday-forked-32lrwm?workspaceId=8a8eeabe-fead-479a-a993-25a9868e8015
Assuming it's because of backwards compatibility: if the original props are mutated to make the props match React18, then the potential cost of doing that seems much higher than just having the ref in two places. At least developers would understand why something is different. The amount of time I had to spend right now in order to try to understand why memoizations don't work – makes me want to cry.
And again, the case in point was with a 3rd party package, so I was controlling only part of the code.
breaking a whole host of downstream memoizations.
I can only think of memoizations when you pass the whole props object. What other memoizations are broken by this?
breaking a whole host of downstream memoizations.
I can only think of memoizations when you pass the whole props object. What other memoizations are broken by this?
That is what we hit. But it has more implications than React.useMemo/useEffect, etc.. Say you have a downstream React.memo component, where you pass parentProps={props}. That wouldn't work. Or if it had a custom equality checker even, it would need to do more work than before, checking for deep equality rather than referential equality of the props.
Minimal repro: https://codesandbox.io/p/sandbox/referentially-equal-props-in-forwardref-7hv78x?file=%2Fpackage.json
Does not reproduce without ref e.g. just <Component />
Bisected down to fa2f82addc7c817892c482792f56a35277e8b75a
@acdlite, I understand the core rationale of the change, but shouldn't this be at minimum highlighted in docs? And aren't there performance considerations re-creating the props object like that on every render that would hurt React 19 vs. older versions? Doesn't really matter for application code, as you can easily drop the use of forwardRef, but what about library authors that need to support React ≤18 who have to stick using forwardRef?
I wish there was at least an escape hatch from this compatibility hack that library authors can decide to opt out of if they're sure that they don't spread props after assigning the ref prop. Could have even been an eslint rule probably to ensure compatibility with React 19, but now I'm not sure what's the right solution now.
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!
@lauri865 https://sitecoreclerical867.github.io/FbYYIEAe-taURKS4RuepKuyuxekSVnZSlkj0FBtpyfMOMWHy9s_2do8/