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

Fix hydration mismatches with React.useId #2566

Merged
merged 2 commits into from Nov 26, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/strange-kids-change.md
@@ -0,0 +1,10 @@
---
'@emotion/core': minor
'@emotion/styled': minor
'@emotion/styled-base': minor
Comment on lines +2 to +4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why minor instead of patch? Don't mind strongly either way, just curious

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've decided to do the same for the types-related change. Kinda doesn't matter but maybe puts less pressure on people to immediately update their code. At the time being they don't have to do the update - they will only have to do that if they start using React 18 and that has not been even released yet.

Dunno, I just sometimes use minor bumps for some additional context than a "new feature". The problem here is that this might affect some people - for example, the serialized output of jest-emotion might change (like it has changed in our tests on this branch here). So this leaves a little bit of breathing room for people so they can stay on the previous version if they want.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good

---

author: @eps1lon
author: @Andarist

Fixed hydration mismatches if `React.useId` (the upcoming API of the React 18) is used within a tree below our components.
emmatown marked this conversation as resolved.
Show resolved Hide resolved
32 changes: 18 additions & 14 deletions packages/core/src/class-names.js
Expand Up @@ -77,6 +77,8 @@ type Props = {
}) => React.Node
}

const Noop = () => null

export const ClassNames = withEmotionCache<Props>((props, context) => {
return (
<ThemeContext.Consumer>
Expand Down Expand Up @@ -112,23 +114,25 @@ export const ClassNames = withEmotionCache<Props>((props, context) => {
let content = { css, cx, theme }
let ele = props.children(content)
hasRendered = true
let possiblyStyleElement = <Noop />
if (!isBrowser && rules.length !== 0) {
return (
<React.Fragment>
<style
{...{
[`data-emotion-${context.key}`]: serializedHashes.substring(
1
),
dangerouslySetInnerHTML: { __html: rules },
nonce: context.sheet.nonce
}}
/>
{ele}
</React.Fragment>
possiblyStyleElement = (
<style
{...{
[`data-emotion-${context.key}`]: serializedHashes.substring(1),
dangerouslySetInnerHTML: { __html: rules },
nonce: context.sheet.nonce
}}
/>
)
}
return ele
// Need to return the same number of siblings or else `React.useId` will cause hydration mismatches.
return (
<React.Fragment>
{possiblyStyleElement}
{ele}
</React.Fragment>
)
}}
</ThemeContext.Consumer>
)
Expand Down
30 changes: 18 additions & 12 deletions packages/core/src/emotion-element.js
Expand Up @@ -56,6 +56,8 @@ export const createEmotionProps = (type: React.ElementType, props: Object) => {
return newProps
}

const Noop = () => null

let render = (cache, props, theme: null | Object, ref) => {
let cssProp = theme === null ? props.css : props.css(theme)

Expand Down Expand Up @@ -112,27 +114,31 @@ let render = (cache, props, theme: null | Object, ref) => {
newProps.className = className

const ele = React.createElement(type, newProps)
let possiblyStyleElement = <Noop />
if (!isBrowser && rules !== undefined) {
let serializedNames = serialized.name
let next = serialized.next
while (next !== undefined) {
serializedNames += ' ' + next.name
next = next.next
}
return (
<>
<style
{...{
[`data-emotion-${cache.key}`]: serializedNames,
dangerouslySetInnerHTML: { __html: rules },
nonce: cache.sheet.nonce
}}
/>
{ele}
</>
possiblyStyleElement = (
<style
{...{
[`data-emotion-${cache.key}`]: serializedNames,
dangerouslySetInnerHTML: { __html: rules },
nonce: cache.sheet.nonce
}}
/>
)
}
return ele
// Need to return the same number of siblings or else `React.useId` will cause hydration mismatches.
return (
<>
{possiblyStyleElement}
{ele}
</>
)
}

// eslint-disable-next-line no-undef
Expand Down
15 changes: 10 additions & 5 deletions packages/jest-emotion/test/__snapshots__/react-enzyme.test.js.snap
Expand Up @@ -10,7 +10,8 @@ exports[`enzyme mount test 1`] = `
}

<Greeting>
<div
<Noop />
<div
className="emotion-0"
>
hello
Expand All @@ -37,7 +38,8 @@ exports[`enzyme test with prop containing css element 1`] = `
}
>
<div>
<p
<Noop />
<p
className="emotion-0"
>
Hello
Expand Down Expand Up @@ -90,7 +92,8 @@ exports[`enzyme test with prop containing css element not at the top level 1`] =
}
>
<div>
<p
<Noop />
<p
className="emotion-0"
id="something"
>
Expand Down Expand Up @@ -129,7 +132,8 @@ exports[`enzyme test with prop containing css element with other label 1`] = `
/>
}
>
<p
<Noop />
<p
className="emotion-0"
id="something"
>
Expand Down Expand Up @@ -161,7 +165,8 @@ exports[`enzyme test with prop containing css element with other props 1`] = `
}
>
<div>
<p
<Noop />
<p
className="emotion-0"
id="something"
>
Expand Down
29 changes: 17 additions & 12 deletions packages/styled-base/src/index.js
Expand Up @@ -17,6 +17,7 @@ You can read more about this here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#ES2018_revision_of_illegal_escape_sequences`

let isBrowser = typeof document !== 'undefined'
const Noop = () => null

let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => {
if (process.env.NODE_ENV !== 'production') {
Expand Down Expand Up @@ -152,27 +153,31 @@ let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => {
}

const ele = React.createElement(finalTag, newProps)
let possiblyStyleElement = <Noop />
if (!isBrowser && rules !== undefined) {
let serializedNames = serialized.name
let next = serialized.next
while (next !== undefined) {
serializedNames += ' ' + next.name
next = next.next
}
return (
<React.Fragment>
<style
{...{
[`data-emotion-${context.key}`]: serializedNames,
dangerouslySetInnerHTML: { __html: rules },
nonce: context.sheet.nonce
}}
/>
{ele}
</React.Fragment>
possiblyStyleElement = (
<style
{...{
[`data-emotion-${context.key}`]: serializedNames,
dangerouslySetInnerHTML: { __html: rules },
nonce: context.sheet.nonce
}}
/>
)
}
return ele
// Need to return the same number of siblings or else `React.useId` will cause hydration mismatches.
return (
<React.Fragment>
{possiblyStyleElement}
{ele}
</React.Fragment>
)
}}
</ThemeContext.Consumer>
)
Expand Down