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

[data grid] React 19 - everything that depends on GridRootPropsContext re-renders on every state change (or root re-render) if the ref prop is present #15770

Open
1 task done
lauri865 opened this issue Dec 6, 2024 · 9 comments · May be fixed by #15955
Labels
bug 🐛 Something doesn't work component: data grid This is the name of the generic UI component, not the React module! React 19 support PRs required to support React 19

Comments

@lauri865
Copy link
Contributor

lauri865 commented Dec 6, 2024

Search keywords

N/A

Latest version

  • I have tested the latest version

Affected products

Data Grid

Steps to reproduce

Steps:

  1. Generic – [React 19] ForwardRef props are not referentially stable, breaking downstream memoizations facebook/react#31613
  2. Datagrid specific – [DataGrid] Improve resize performance #15549 (comment)

Current behavior

Read more about on the links above.

Found in the midst of another PR, so opening an issue so it doesn't go overlooked, especially now that there's official React 19 support and React 19 is stable.

Worth noting that it can also affect instances where developers don't actually provide a ref, but have created an abstracted component over the Datagrid which passes forward the ref prop by explicitly naming it as ref={ref} even if ref={undefined}. This was the case for us. Which means that the potential share of users affected is much wider.

Not sure if the problem affects any other areas of the DataGrid as well, but probably not as critical as this.

cc @arminmeh, since you worked on the React 19 support

@lauri865 lauri865 added bug 🐛 Something doesn't work status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Dec 6, 2024
@github-actions github-actions bot added the component: data grid This is the name of the generic UI component, not the React module! label Dec 6, 2024
@lauri865 lauri865 changed the title [DataGrid] React 19 - everything that depends on GridRootPropsContext re-renders if ref prop is present [DataGrid] React 19 - everything that depends on GridRootPropsContext re-renders on ever state change if ref prop is present Dec 6, 2024
@lauri865 lauri865 changed the title [DataGrid] React 19 - everything that depends on GridRootPropsContext re-renders on ever state change if ref prop is present [DataGrid] React 19 - everything that depends on GridRootPropsContext re-renders on every state change (or root re-render) if the ref prop is present Dec 6, 2024
@michelengelen
Copy link
Member

Thanks for opening it @lauri865 ... you're adopting R19 pretty swiftly, very nice!

I'll forward it to the team and add it to the board! 👍🏼

@michelengelen michelengelen added React 19 support PRs required to support React 19 and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Dec 6, 2024
@github-project-automation github-project-automation bot moved this to 🆕 Needs refinement in MUI X Data Grid Dec 6, 2024
@michelengelen michelengelen changed the title [DataGrid] React 19 - everything that depends on GridRootPropsContext re-renders on every state change (or root re-render) if the ref prop is present [data grid] React 19 - everything that depends on GridRootPropsContext re-renders on every state change (or root re-render) if the ref prop is present Dec 6, 2024
@cherniavskii
Copy link
Member

It looks like there's nothing we can do about it on our side until it's fixed in React 😕

@lauri865
Copy link
Contributor Author

lauri865 commented Dec 6, 2024

A breaking fix on Datagrid's end in v8 could be to have a prop rootRef or gridRef for backwards compatibility (i.e. skipping the forwardRef wrapping), yet enabling the use of the ref prop with React 19 still. The only other alternative is to change how rootProps get memoized, but that's maybe not the preferred solution.

I wouldn't hold my breath waiting after React 🙊

Fwiw, we're just running a locally patched version of the Datagrid that strips out the forwardRef wrapper.

@romgrk
Copy link
Contributor

romgrk commented Dec 6, 2024

If React doesn't fix it, then yes the rootRef approach could be nice. Meanwhile we could maybe do something with objectShallowCompare to preserve props & prevent re-renders. But I'd wait a bit to see what they want to do upstream.

@lauri865
Copy link
Contributor Author

lauri865 commented Dec 6, 2024

Alternatively, maybe something like that could work?

const forwardRefBackwardsCompatible = <T, P>(
  render: React.ForwardRefRenderFunction<T, P>,
) => {
  if (React.version.startsWith("19")) {
    const Component = (props: any) => render(props, props.ref ?? null);
    return Component;
  }

  return React.forwardRef(render);
};

If it works, it'd probably be more performant than creating a new object on every render in React 19 and then trying to memoize it / shallow compare it.

@lauri865
Copy link
Contributor Author

lauri865 commented Dec 6, 2024

And a reason why I wouldn't count on an upstream change is that the fix would likely mean breaking the below pattern:

const Test = React.forwardRef((props, refProp) => {
  const internalRef = React.useRef(null);
  const ref = useMergedRef(refProp, internalRef);
  return <div ref={ref} {...props}>Test</div>;
})

<Test ref={React.useRef()} />

Internal ref would be never be set now in React 19, because props are spread after the ref and props contain the ref prop as well. Since React 19 is a stable release now, making that change would be breaking. And frankly, between the two, one breaks memoization in an edge case, the other can alter rendering altogether or break components, so you can guess which of the two bads they'll opt for.

Still, it should come with a big fat red warning label though, as the new behaviour is very unclear and undocumented.

@dmytro-shchurov
Copy link

dmytro-shchurov commented Dec 13, 2024

And a reason why I wouldn't count on an upstream change is that the fix would likely mean breaking the below pattern:

I would call this an antipattern, cause this is quite naive to specify overrides prior to a spread operator. If such exists in mui code it must be refactored IMHO

@lauri865
Copy link
Contributor Author

lauri865 commented Dec 13, 2024

And a reason why I wouldn't count on an upstream change is that the fix would likely mean breaking the below pattern:

I would call this an antipattern, cause this is quite naive to specify overrides prior to a spread operator. If such exists in mui code it must be refactored IMHO

Well, prior to React 19 it wasn't an override, as props could never contain a ref. Now it is. Which is the core reason why this change exists in React. Agree that it should be removed in Mui if it exists going forward, but it doesn't change anything for upstream and the issue remains so long as forwardRef is used within React 19.

@romgrk
Copy link
Contributor

romgrk commented Dec 16, 2024

Alternatively, maybe something like that could work?

I'm happy with that solution, and yes considering the facts it's likely React won't fix it, so we can go ahead with that.

@romgrk romgrk moved this from 🆕 Needs refinement to 🔖 Ready in MUI X Data Grid Dec 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 Something doesn't work component: data grid This is the name of the generic UI component, not the React module! React 19 support PRs required to support React 19
Projects
Status: 🔖 Ready
Development

Successfully merging a pull request may close this issue.

5 participants