emotion-js / emotion

👩‍🎤 CSS-in-JS library designed for high performance style composition
https://emotion.sh/
MIT License
17.32k stars 1.1k forks source link

React 19 styled() override ref of host #3204

Open oliviertassinari opened 2 days ago

oliviertassinari commented 2 days ago

To reproduce:

import * as React from "react";
import * as ReactDOM from "react-dom/client";
import styled from "@emotion/styled";

function Button(props) {
  const buttonRef = React.useRef();

  React.useEffect(() => {
    buttonRef.current.focus();
  }, []);

  console.log("React", props.ref);

  return (
    <button ref={buttonRef} {...props}>
      Hello
    </button>
  );
}

const SyledButton = styled(Button)({
  color: "red",
});

const App = () => {
  return <SyledButton>Hello</SyledButton>;
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Current behavior:

React 19.0.0-rc.0 fail: https://codesandbox.io/p/sandbox/blissful-danilo-3z4rs3?file=%2Fsrc%2Findex.js%3A1%2C1-35%2C1

SCR-20240703-bwmt

Expected behavior:

React 18.3.1 success: https://codesandbox.io/p/sandbox/peaceful-jones-fqj8kw?file=%2Fpackage.json%3A5%2C12&workspaceId=836800c1-9711-491c-a89b-3ac24cbd8cd8

SCR-20240703-bwqv

Environment information:

Context

I have noticed this from https://github.com/mui/material-ui/issues/41388. React 19 handles ref as prop by default https://react.dev/blog/2024/04/25/react-19#ref-as-a-prop. So when emotion does https://github.com/emotion-js/emotion/blob/4cc565f1b7a576902b6360654afa4ce176698f76/packages/styled/src/base.js#L160 it overrides the ref used by the component.

The right solution seems to do:

+     // The ref is coming from a React.forwardRef. It might not exist. Since React 19 handles ref as prop, only define it if there is a value.
+     if (ref) {
        newProps.ref = ref;
+     }

since emotion is only trying to be transparent from a ref perspective, with it's styled() wrapper.

https://github.com/emotion-js/emotion/blob/4cc565f1b7a576902b6360654afa4ce176698f76/packages/react/src/context.js#L35

Andarist commented 2 days ago

Could you make those codesandboxes public?

oliviertassinari commented 2 days ago

Ah god, I should default to StackBlitz. Fixed. CodeSandbox fixed a bit it's UX recently by showing the sandbox visibility setting with an icon, but I keep forgetting about it (so are people on Material UI). There should be a setting to configure a default mode in CodeSandbox.