Skip to content

Commit

Permalink
Fix hydration mismatches with React.useId (#2566)
Browse files Browse the repository at this point in the history
* Fix hydration mismatches with `React.useId`

* Update .changeset/strange-kids-change.md

Co-authored-by: Mitchell Hamilton <mitchell@hamil.town>
  • Loading branch information
Andarist and emmatown committed Nov 26, 2021
1 parent 960de78 commit 122e9f1
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 43 deletions.
10 changes: 10 additions & 0 deletions .changeset/strange-kids-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@emotion/core': minor
'@emotion/styled': minor
'@emotion/styled-base': minor
---

author: @eps1lon
author: @Andarist

Fixed hydration mismatches if `React.useId` (an upcoming API in React 18) is used within a tree below our components.
32 changes: 18 additions & 14 deletions packages/core/src/class-names.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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

0 comments on commit 122e9f1

Please sign in to comment.