ben-rogerson / twin.macro

🦹‍♂️ Twin blends the magic of Tailwind with the flexibility of css-in-js (emotion, styled-components, solid-styled-components, stitches and goober) at build time.
MIT License
7.92k stars 183 forks source link

`className` mismatch between server and client while using emotion #750

Closed kaguya3222 closed 1 year ago

kaguya3222 commented 1 year ago

What is happening

I use Next.js + twin.macro + emotion. If I refresh the page I get an error about client and server mismatch of className attribute.

How to reproduce

  1. Open this reproduction or clone this repo.
  2. Install dependencies, if you cloned the repo.
  3. Reload the index page and see warning in console about className mismatch.

What is expected

No warnings or errors in console

Error (example)

next-dev.js?3515:20 Warning: Prop `className` did not match. Server: "css-1b7g2n7-Container-Container-Container e1mc56xh0" Client: "css-1p0a8i8-Container-Container-Container-Container e1mc56xh0"
    at div
    at eval (webpack-internal:///./node_modules/@emotion/react/dist/emotion-element-6a883da9.browser.esm.js:57:66)
    at div
    at Home
    at MyApp (webpack-internal:///./pages/_app.tsx:27:25)
    at ErrorBoundary (webpack-internal:///./node_modules/next/dist/compiled/@next/react-dev-overlay/dist/client.js:8:20742)
    at ReactDevOverlay (webpack-internal:///./node_modules/next/dist/compiled/@next/react-dev-overlay/dist/client.js:8:23635)
    at Container (webpack-internal:///./node_modules/next/dist/client/index.js:111:5)
    at AppContainer (webpack-internal:///./node_modules/next/dist/client/index.js:296:24)
    at Root (webpack-internal:///./node_modules/next/dist/client/index.js:500:25) 

See more info here: https://nextjs.org/docs/messages/react-hydration-error

Additional context

The code that produces this issue is located in pages/index.tsx, 3rd line. When I use emotion styled components + emotion css (that are imported from twin.macro) + props, then I get this error.

Example:

import { styled, css } from "twin.macro";

const Container = styled("div")(({ color }) => [
  css`
    color: ${color};
  `,
]);

This issue can be reproduced in twin.examples: https://github.com/ben-rogerson/twin.examples/tree/master/next-emotion-typescript

If I change import to this (code below) then everything works without warnings.

import styled from "@emotion/styled";
import { css } from "@emotion/react";

Also, if I remove props from styled component, then it also works without warnings:

import { styled, css } from "twin.macro";

const Container = styled("div")(() => [
  css`
    color: red;
  `,
]);
ben-rogerson commented 1 year ago

Thanks for the great issue writeup 👍

I've taken a look into this and haven't been able to work out a fix - it's quite strange. The only thing I can think of - is that the reference to css is lost during the conversion.

Here's the only conversion that takes place with your code above:

import { css as _css } from "@emotion/react";
import _styled from "@emotion/styled";

const Container = _styled("div")(({
  color
}) => [_css`
    color: ${color};
`]);

Here's some workarounds I've found:

// Use css object styles
const Container = styled("div")(({ color }) => [css({ color: color })]);

// Or move `css` out of `styled`
const makeColor = (color) => css`
  color: ${color};
`;
const Container = styled("div")(({ color }) => [makeColor(color)]);
kaguya3222 commented 1 year ago

@ben-rogerson Thank you for the response! Comparing the workarounds I think the best is to explicitly import styled and css from @emotion packages, not from twin.macro.

import styled from "@emotion/styled";
import { css } from "@emotion/react";

What do you think ?

I think it's okey because twin.macro does this under the hood and also it's more explicit way for new developers to say what css-in-js we use

ben-rogerson commented 1 year ago

Yeah you can avoid the twin imports altogether - can't think of any side-effects - you'll just have more imports per file.

Worth asking - which issues made removing css from inside styled a no-go 🙅 ?

const Container = styled("div")(() => [
  css` // < Without css?
    color: red;
  `,
]);
kaguya3222 commented 1 year ago

@ben-rogerson I think there will be no issues, but I've understood that for me and my team it's better to use only css, without styled, so we'll go this way :)