diff --git a/.changeset/purple-tigers-breathe.md b/.changeset/purple-tigers-breathe.md
new file mode 100644
index 000000000..80e500a4f
--- /dev/null
+++ b/.changeset/purple-tigers-breathe.md
@@ -0,0 +1,8 @@
+---
+'@emotion/jest': minor
+---
+
+author: @eps1lon
+author: @Andarist
+
+Adjusted the serialization logic to unwrap rendered elements from Fragments that had to be added to fix hydration mismatches caused by `React.useId` usage (the upcoming API of the React 18).
diff --git a/.changeset/strange-kids-change.md b/.changeset/strange-kids-change.md
new file mode 100644
index 000000000..3548c9d29
--- /dev/null
+++ b/.changeset/strange-kids-change.md
@@ -0,0 +1,6 @@
+---
+'@emotion/react': minor
+'@emotion/styled': minor
+---
+
+Fixed hydration mismatches if `React.useId` (the upcoming API of the React 18) is used within a tree below our components.
diff --git a/package.json b/package.json
index 879aefcb2..6c9f77c5d 100644
--- a/package.json
+++ b/package.json
@@ -261,6 +261,8 @@
"react-router-dom": "^4.2.2",
"react-scripts": "1.1.5",
"react-test-renderer": "16.8.6",
+ "react18": "npm:react@alpha",
+ "react18-dom": "npm:react-dom@alpha",
"svg-tag-names": "^1.1.1",
"through": "^2.3.8",
"unified": "^6.1.6",
diff --git a/packages/jest/src/create-enzyme-serializer.js b/packages/jest/src/create-enzyme-serializer.js
index 9208b49b3..52ec73be7 100644
--- a/packages/jest/src/create-enzyme-serializer.js
+++ b/packages/jest/src/create-enzyme-serializer.js
@@ -3,8 +3,66 @@ import type { Options } from './create-serializer'
import { createSerializer as createEmotionSerializer } from './create-serializer'
import * as enzymeTickler from './enzyme-tickler'
import { createSerializer as createEnzymeToJsonSerializer } from 'enzyme-to-json'
+import {
+ isEmotionCssPropElementType,
+ isStyledElementType,
+ unwrapFromPotentialFragment
+} from './utils'
-const enzymeSerializer = createEnzymeToJsonSerializer({})
+const enzymeToJsonSerializer = createEnzymeToJsonSerializer({
+ map: json => {
+ if (typeof json.node.type === 'string') {
+ return json
+ }
+ const isRealStyled = json.node.type.__emotion_real === json.node.type
+ if (isRealStyled) {
+ return {
+ ...json,
+ children: json.children.slice(-1)
+ }
+ }
+ return json
+ }
+})
+
+// this is a hack, leveraging the internal/implementation knowledge about the enzyme's ShallowWrapper
+// there is no sane way to get this information otherwise though
+const getUnrenderedElement = shallowWrapper => {
+ const symbols = Object.getOwnPropertySymbols(shallowWrapper)
+ const elementValues = symbols.filter(sym => {
+ const val = shallowWrapper[sym]
+ return !!val && val.$$typeof === Symbol.for('react.element')
+ })
+ if (elementValues.length !== 1) {
+ throw new Error(
+ "Could not get unrendered element reliably from the Enzyme's ShallowWrapper. This is a bug in Emotion - please open an issue with repro steps included:\n" +
+ 'https://github.com/emotion-js/emotion/issues/new?assignees=&labels=bug%2C+needs+triage&template=--bug-report.md&title='
+ )
+ }
+ return shallowWrapper[elementValues[0]]
+}
+
+const wrappedEnzymeSerializer = {
+ test: enzymeToJsonSerializer.test,
+ print: (enzymeWrapper, printer) => {
+ const isShallow = !!enzymeWrapper.dive
+
+ if (isShallow && enzymeWrapper.root() === enzymeWrapper) {
+ const unrendered = getUnrenderedElement(enzymeWrapper)
+ if (
+ isEmotionCssPropElementType(unrendered) ||
+ isStyledElementType(unrendered)
+ ) {
+ return enzymeToJsonSerializer.print(
+ unwrapFromPotentialFragment(enzymeWrapper),
+ printer
+ )
+ }
+ }
+
+ return enzymeToJsonSerializer.print(enzymeWrapper, printer)
+ }
+}
export function createEnzymeSerializer({
classNameReplacer,
@@ -16,7 +74,7 @@ export function createEnzymeSerializer({
})
return {
test(node: *) {
- return enzymeSerializer.test(node) || emotionSerializer.test(node)
+ return wrappedEnzymeSerializer.test(node) || emotionSerializer.test(node)
},
serialize(
node: *,
@@ -26,9 +84,9 @@ export function createEnzymeSerializer({
refs: *,
printer: Function
) {
- if (enzymeSerializer.test(node)) {
+ if (wrappedEnzymeSerializer.test(node)) {
const tickled = enzymeTickler.tickle(node)
- return enzymeSerializer.print(
+ return wrappedEnzymeSerializer.print(
tickled,
// https://github.com/facebook/jest/blob/470ef2d29c576d6a10de344ec25d5a855f02d519/packages/pretty-format/src/index.ts#L281
valChild => printer(valChild, config, indentation, depth, refs)
diff --git a/packages/jest/src/create-serializer.js b/packages/jest/src/create-serializer.js
index 97c070dfd..75b984c34 100644
--- a/packages/jest/src/create-serializer.js
+++ b/packages/jest/src/create-serializer.js
@@ -115,46 +115,45 @@ function isShallowEnzymeElement(
})
}
-const createConvertEmotionElements =
- (keys: string[], printer: *) => (node: any) => {
- if (isPrimitive(node)) {
- return node
- }
- if (isEmotionCssPropEnzymeElement(node)) {
- const className = enzymeTickler.getTickledClassName(node.props.css)
- const labels = getLabelsFromClassName(keys, className || '')
-
- if (isShallowEnzymeElement(node, keys, labels)) {
- const emotionType = node.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__
- // emotionType will be a string for DOM elements
- const type =
- typeof emotionType === 'string'
- ? emotionType
- : emotionType.displayName || emotionType.name || 'Component'
- return {
- ...node,
- props: filterEmotionProps({
- ...node.props,
- className
- }),
- type
- }
- } else {
- return node.children[0]
- }
- }
- if (isEmotionCssPropElementType(node)) {
+const createConvertEmotionElements = (keys: string[]) => (node: any) => {
+ if (isPrimitive(node)) {
+ return node
+ }
+ if (isEmotionCssPropEnzymeElement(node)) {
+ const className = enzymeTickler.getTickledClassName(node.props.css)
+ const labels = getLabelsFromClassName(keys, className || '')
+
+ if (isShallowEnzymeElement(node, keys, labels)) {
+ const emotionType = node.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__
+ // emotionType will be a string for DOM elements
+ const type =
+ typeof emotionType === 'string'
+ ? emotionType
+ : emotionType.displayName || emotionType.name || 'Component'
return {
...node,
- props: filterEmotionProps(node.props),
- type: node.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__
+ props: filterEmotionProps({
+ ...node.props,
+ className
+ }),
+ type
}
+ } else {
+ return node.children[node.children.length - 1]
}
- if (isReactElement(node)) {
- return copyProps({}, node)
+ }
+ if (isEmotionCssPropElementType(node)) {
+ return {
+ ...node,
+ props: filterEmotionProps(node.props),
+ type: node.props.__EMOTION_TYPE_PLEASE_DO_NOT_USE__
}
- return node
}
+ if (isReactElement(node)) {
+ return copyProps({}, node)
+ }
+ return node
+}
function clean(node: any, classNames: string[]) {
if (Array.isArray(node)) {
@@ -199,7 +198,7 @@ export function createSerializer({
) {
const elements = getStyleElements()
const keys = getKeys(elements)
- const convertEmotionElements = createConvertEmotionElements(keys, printer)
+ const convertEmotionElements = createConvertEmotionElements(keys)
const converted = deepTransform(val, convertEmotionElements)
const nodes = getNodes(converted)
const classNames = getClassNamesFromNodes(nodes)
diff --git a/packages/jest/src/enzyme-tickler.js b/packages/jest/src/enzyme-tickler.js
index 6daacfb82..45b52f72c 100644
--- a/packages/jest/src/enzyme-tickler.js
+++ b/packages/jest/src/enzyme-tickler.js
@@ -1,3 +1,5 @@
+import { unwrapFromPotentialFragment } from './utils'
+
const tickledCssProps = new WeakMap()
export const getTickledClassName = cssProp => tickledCssProps.get(cssProp)
@@ -12,8 +14,11 @@ export const tickle = wrapper => {
return
}
- const wrapped = (isShallow ? el.dive() : el.children()).first()
- tickledCssProps.set(cssProp, wrapped.props().className)
+ const rendered = (isShallow ? el.dive() : el.children()).last()
+ tickledCssProps.set(
+ cssProp,
+ unwrapFromPotentialFragment(rendered).props().className
+ )
})
return wrapper
}
diff --git a/packages/jest/src/utils.js b/packages/jest/src/utils.js
index 7b0d71cb6..6eb7dbd14 100644
--- a/packages/jest/src/utils.js
+++ b/packages/jest/src/utils.js
@@ -58,7 +58,15 @@ function getClassNameProp(node) {
return (node && node.prop('className')) || ''
}
-function getClassNamesFromEnzyme(selectors, node) {
+export function unwrapFromPotentialFragment(node: *) {
+ if (node.type() === Symbol.for('react.fragment')) {
+ return node.children().last()
+ }
+ return node
+}
+
+function getClassNamesFromEnzyme(selectors, nodeWithPotentialFragment) {
+ const node = unwrapFromPotentialFragment(nodeWithPotentialFragment)
// We need to dive in to get the className if we have a styled element from a shallow render
const isShallow = shouldDive(node)
const nodeWithClassName = findNodeWithClassName(
@@ -86,11 +94,18 @@ export function isReactElement(val: any): boolean {
export function isEmotionCssPropElementType(val: any): boolean {
return (
val.$$typeof === Symbol.for('react.element') &&
- val.type.$$typeof === Symbol.for('react.forward_ref') &&
val.type.displayName === 'EmotionCssPropInternal'
)
}
+export function isStyledElementType(val: any): boolean {
+ if (val.$$typeof !== Symbol.for('react.element')) {
+ return false
+ }
+ const { type } = val
+ return type.__emotion_real === type
+}
+
export function isEmotionCssPropEnzymeElement(val: any): boolean {
return (
val.$$typeof === Symbol.for('react.test.json') &&
diff --git a/packages/jest/test/__snapshots__/react-enzyme.test.js.snap b/packages/jest/test/__snapshots__/react-enzyme.test.js.snap
index 28bcdbefe..d7dc8a6ea 100644
--- a/packages/jest/test/__snapshots__/react-enzyme.test.js.snap
+++ b/packages/jest/test/__snapshots__/react-enzyme.test.js.snap
@@ -56,6 +56,67 @@ exports[`enzyme mount empty styled 1`] = `
`;
+exports[`enzyme mount fragment with multiple css prop elements 1`] = `
+.emotion-0 {
+ background-color: hotpink;
+}
+
+.emotion-1 {
+ background-color: green;
+}
+
+.emotion-2 {
+ background-color: blue;
+}
+
+
+
+
+
+
+`;
+
+exports[`enzyme mount multiple selected components 1`] = `
+Array [
+ .emotion-0 {
+ background-color: hotpink;
+}
+
+
+ hello
+ ,
+ .emotion-0 {
+ background-color: blue;
+}
+
+
+ beautiful
+ ,
+ .emotion-0 {
+ background-color: green;
+}
+
+
+ world
+ ,
+]
+`;
+
exports[`enzyme mount nested 1`] = `
.emotion-0 {
background-color: red;
@@ -129,6 +190,25 @@ exports[`enzyme mount nested styled with css prop 1`] = `
`;
+exports[`enzyme mount parent and child using css property 1`] = `
+.emotion-0 {
+ background-color: black;
+}
+
+.emotion-1 {
+ color: white;
+}
+
+
+`;
+
exports[`enzyme mount styled 1`] = `
.emotion-0 {
background-color: red;
@@ -208,6 +288,8 @@ exports[`enzyme mount theming 1`] = `
`;
+exports[`enzyme mount unmatched selector 1`] = `null`;
+
exports[`enzyme mount with array of styles as css prop 1`] = `
.emotion-0 {
background-color: black;
@@ -388,25 +470,6 @@ exports[`enzyme mount with styles on top level 1`] = `
`;
-exports[`enzyme parent and child using css property 1`] = `
-.emotion-0 {
- background-color: black;
-}
-
-.emotion-1 {
- color: white;
-}
-
-
-`;
-
exports[`enzyme shallow basic 1`] = `
.emotion-0 {
background-color: red;
@@ -453,6 +516,67 @@ exports[`enzyme shallow empty styled 1`] = `
`;
+exports[`enzyme shallow fragment with multiple css prop elements 1`] = `
+.emotion-0 {
+ background-color: hotpink;
+}
+
+.emotion-1 {
+ background-color: green;
+}
+
+.emotion-2 {
+ background-color: blue;
+}
+
+
+
+
+
+
+`;
+
+exports[`enzyme shallow multiple selected components 1`] = `
+Array [
+ .emotion-0 {
+ background-color: hotpink;
+}
+
+
+ hello
+ ,
+ .emotion-0 {
+ background-color: blue;
+}
+
+
+ beautiful
+ ,
+ .emotion-0 {
+ background-color: green;
+}
+
+
+ world
+ ,
+]
+`;
+
exports[`enzyme shallow nested 1`] = `
@@ -495,6 +619,25 @@ exports[`enzyme shallow nested styled with css prop 1`] = `
`;
+exports[`enzyme shallow parent and child using css property 1`] = `
+.emotion-0 {
+ background-color: black;
+}
+
+.emotion-1 {
+ color: white;
+}
+
+
+`;
+
exports[`enzyme shallow styled 1`] = `
.emotion-0 {
background-color: red;
@@ -550,6 +693,8 @@ exports[`enzyme shallow theming 1`] = `
`;
+exports[`enzyme shallow unmatched selector 1`] = `null`;
+
exports[`enzyme shallow with array of styles as css prop 1`] = `
.emotion-0 {
background-color: black;
@@ -676,10 +821,13 @@ exports[`enzyme with prop containing css element in fragment 1`] = `
x
-
- y
-
+ Array [
+ "",
+
+ y
+
,
+ ]
`;
diff --git a/packages/jest/test/react-enzyme.test.js b/packages/jest/test/react-enzyme.test.js
index 2e772a5c6..76888ddad 100644
--- a/packages/jest/test/react-enzyme.test.js
+++ b/packages/jest/test/react-enzyme.test.js
@@ -14,6 +14,8 @@ import * as serializer from '@emotion/jest/enzyme-serializer'
expect.extend(matchers)
expect.addSnapshotSerializer(serializer)
+const identity = v => v
+
const cases = {
basic: {
render() {
@@ -278,47 +280,94 @@ const cases = {
)
}
+ },
+ 'parent and child using css property': {
+ render() {
+ const parentStyle = css`
+ background-color: black;
+ `
+
+ const childStyle = css`
+ color: white;
+ `
+
+ return (
+
+ )
+ }
+ },
+ 'fragment with multiple css prop elements': {
+ render() {
+ const Component = () => {
+ return (
+ <>
+
+
+
+ >
+ )
+ }
+ return
+ }
+ },
+ 'multiple selected components': {
+ selector: tree =>
+ // with simple `tree.find('[data-item]')` we get elements twice with `mount` since it selects both the css prop element and the host element
+ tree.findWhere(
+ n => typeof n.type() !== 'string' && n.props()['data-item']
+ ),
+ render() {
+ return (
+
+ -
+ {'hello'}
+
+ -
+ {'beautiful'}
+
+ -
+ {'world'}
+
+
+ )
+ }
+ },
+ 'unmatched selector': {
+ selector: tree => tree.find('div'),
+ render() {
+ return (
+
+ - {'hello'}
+ - {'beautiful'}
+ - {'world'}
+
+ )
+ }
}
}
describe('enzyme', () => {
jestInCase(
'shallow',
- ({ render }) => {
+ ({ render, selector = identity }) => {
const wrapper = enzyme.shallow(render())
- expect(wrapper).toMatchSnapshot()
+ expect(selector(wrapper)).toMatchSnapshot()
},
cases
)
jestInCase(
'mount',
- ({ render }) => {
+ ({ render, selector = identity }) => {
const wrapper = enzyme.mount(render())
- expect(wrapper).toMatchSnapshot()
+ expect(selector(wrapper)).toMatchSnapshot()
},
cases
)
- test('parent and child using css property', () => {
- const parentStyle = css`
- background-color: black;
- `
-
- const childStyle = css`
- color: white;
- `
-
- const wrapper = enzyme.mount(
-
- )
-
- expect(wrapper).toMatchSnapshot()
- })
-
test('with prop containing css element in fragment', () => {
const FragmentComponent = () => (
diff --git a/packages/react/__tests__/rehydration.js b/packages/react/__tests__/rehydration.js
index 1245ec161..32d300afc 100644
--- a/packages/react/__tests__/rehydration.js
+++ b/packages/react/__tests__/rehydration.js
@@ -17,8 +17,10 @@ let ReactDOMServer
let createCache
let css
let jsx
+let styled
let CacheProvider
let Global
+let ClassNames
let createEmotionServer
const resetAllModules = () => {
@@ -34,7 +36,9 @@ const resetAllModules = () => {
jsx = emotionReact.jsx
CacheProvider = emotionReact.CacheProvider
Global = emotionReact.Global
+ ClassNames = emotionReact.ClassNames
createEmotionServer = require('@emotion/server/create-instance').default
+ styled = require('@emotion/styled').default
}
const removeGlobalProp = prop => {
@@ -61,6 +65,11 @@ const disableBrowserEnvTemporarily = (fn: () => T): T => {
}
}
+beforeEach(() => {
+ safeQuerySelector('head').innerHTML = ''
+ safeQuerySelector('body').innerHTML = ''
+})
+
test("cache created in render doesn't cause a hydration mismatch", () => {
safeQuerySelector('body').innerHTML = [
'',
@@ -477,7 +486,6 @@ test('duplicated global styles can be removed safely after rehydrating HTML SSRe
}
})
- safeQuerySelector('head').innerHTML = ''
safeQuerySelector('body').innerHTML = `
${app}
`
expect(safeQuerySelector('html')).toMatchInlineSnapshot(`
@@ -592,3 +600,156 @@ test('duplicated global styles can be removed safely after rehydrating HTML SSRe
`)
})
+
+describe('react18', () => {
+ let previousIsReactActEnvironment
+ beforeAll(() => {
+ jest
+ .mock('react', () => {
+ return jest.requireActual('react18')
+ })
+ .mock('react-dom', () => {
+ return jest.requireActual('react18-dom')
+ })
+ .mock('react-dom/server', () => {
+ return jest.requireActual('react18-dom/server')
+ })
+
+ previousIsReactActEnvironment = global.IS_REACT_ACT_ENVIRONMENT
+ global.IS_REACT_ACT_ENVIRONMENT = true
+ })
+
+ afterAll(() => {
+ jest.clearAllMocks()
+ global.IS_REACT_ACT_ENVIRONMENT = previousIsReactActEnvironment
+ })
+
+ test('no hydration mismatch for styled when using useId', () => {
+ const finalHTML = disableBrowserEnvTemporarily(() => {
+ resetAllModules()
+
+ const StyledDivWithId = styled(function DivWithId({ className }) {
+ const id = (React: any).useId()
+ return
+ })({
+ border: '1px solid black'
+ })
+
+ return ReactDOMServer.renderToString(
)
+ })
+
+ safeQuerySelector('body').innerHTML = `
${finalHTML}
`
+
+ resetAllModules()
+
+ const StyledDivWithId = styled(function DivWithId({ className }) {
+ const id = (React: any).useId()
+ return
+ })({
+ border: '1px solid black'
+ })
+
+ ;(React: any).unstable_act(() => {
+ ReactDOM.hydrateRoot(safeQuerySelector('#root'),
)
+ })
+
+ expect((console.error: any).mock.calls).toMatchInlineSnapshot(`Array []`)
+ expect((console.warn: any).mock.calls).toMatchInlineSnapshot(`Array []`)
+ })
+
+ test('no hydration mismatch for css prop when using useId', () => {
+ const finalHTML = disableBrowserEnvTemporarily(() => {
+ resetAllModules()
+
+ function DivWithId({ className }: { className?: string }) {
+ const id = (React: any).useId()
+ return
+ }
+
+ return ReactDOMServer.renderToString(
+
+ )
+ })
+
+ safeQuerySelector('body').innerHTML = `
${finalHTML}
`
+
+ resetAllModules()
+
+ function DivWithId({ className }: { className?: string }) {
+ const id = (React: any).useId()
+ return
+ }
+
+ ;(React: any).unstable_act(() => {
+ ReactDOM.hydrateRoot(
+ safeQuerySelector('#root'),
+
+ )
+ })
+
+ expect((console.error: any).mock.calls).toMatchInlineSnapshot(`Array []`)
+ expect((console.warn: any).mock.calls).toMatchInlineSnapshot(`Array []`)
+ })
+
+ test('no hydration mismatch for ClassNames when using useId', () => {
+ const finalHTML = disableBrowserEnvTemporarily(() => {
+ resetAllModules()
+
+ const DivWithId = ({ className }) => {
+ const id = (React: any).useId()
+ return
+ }
+
+ return ReactDOMServer.renderToString(
+
+ {({ css }) => {
+ return (
+
+ )
+ }}
+
+ )
+ })
+
+ safeQuerySelector('body').innerHTML = `
${finalHTML}
`
+
+ resetAllModules()
+
+ const DivWithId = ({ className }) => {
+ const id = (React: any).useId()
+ return
+ }
+
+ ;(React: any).unstable_act(() => {
+ ReactDOM.hydrateRoot(
+ safeQuerySelector('#root'),
+
+ {({ css }) => {
+ return (
+
+ )
+ }}
+
+ )
+ })
+
+ expect((console.error: any).mock.calls).toMatchInlineSnapshot(`Array []`)
+ expect((console.warn: any).mock.calls).toMatchInlineSnapshot(`Array []`)
+ })
+})
diff --git a/packages/react/src/class-names.js b/packages/react/src/class-names.js
index 1ac618f3b..deabcdce7 100644
--- a/packages/react/src/class-names.js
+++ b/packages/react/src/class-names.js
@@ -88,6 +88,8 @@ type Props = {
}) => React.Node
}
+const Noop = () => null
+
export const ClassNames: React.AbstractComponent
=
/* #__PURE__ */ withEmotionCache((props, cache) => {
let rules = ''
@@ -125,21 +127,25 @@ export const ClassNames: React.AbstractComponent =
}
let ele = props.children(content)
hasRendered = true
+ let possiblyStyleElement =
if (!isBrowser && rules.length !== 0) {
- return (
- <>
-
- {ele}
- >
+ possiblyStyleElement = (
+
)
}
- return ele
+ // Need to return the same number of siblings or else `React.useId` will cause hydration mismatches.
+ return (
+ <>
+ {possiblyStyleElement}
+ {ele}
+ >
+ )
})
if (process.env.NODE_ENV !== 'production') {
diff --git a/packages/react/src/emotion-element.js b/packages/react/src/emotion-element.js
index 8330c14ac..8514f9376 100644
--- a/packages/react/src/emotion-element.js
+++ b/packages/react/src/emotion-element.js
@@ -57,6 +57,8 @@ export const createEmotionProps = (type: React.ElementType, props: Object) => {
return newProps
}
+const Noop = () => null
+
let Emotion = /* #__PURE__ */ withEmotionCache(
(props, cache, ref) => {
let cssProp = props.css
@@ -121,6 +123,7 @@ let Emotion = /* #__PURE__ */ withEmotionCache(
newProps.className = className
const ele = React.createElement(type, newProps)
+ let possiblyStyleElement =
if (!isBrowser && rules !== undefined) {
let serializedNames = serialized.name
let next = serialized.next
@@ -128,20 +131,23 @@ let Emotion = /* #__PURE__ */ withEmotionCache(
serializedNames += ' ' + next.name
next = next.next
}
- return (
- <>
-
- {ele}
- >
+ possiblyStyleElement = (
+
)
}
- return ele
+ // Need to return the same number of siblings or else `React.useId` will cause hydration mismatches.
+ return (
+ <>
+ {possiblyStyleElement}
+ {ele}
+ >
+ )
}
)
diff --git a/packages/styled/src/base.js b/packages/styled/src/base.js
index 292ecaad6..d2ea17900 100644
--- a/packages/styled/src/base.js
+++ b/packages/styled/src/base.js
@@ -18,6 +18,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') {
@@ -132,6 +133,7 @@ let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => {
newProps.ref = ref
const ele = React.createElement(finalTag, newProps)
+ let possiblyStyleElement =
if (!isBrowser && rules !== undefined) {
let serializedNames = serialized.name
let next = serialized.next
@@ -139,20 +141,23 @@ let createStyled: CreateStyled = (tag: any, options?: StyledOptions) => {
serializedNames += ' ' + next.name
next = next.next
}
- return (
- <>
-
- {ele}
- >
+ possiblyStyleElement = (
+
)
}
- return ele
+ // Need to return the same number of siblings or else `React.useId` will cause hydration mismatches.
+ return (
+ <>
+ {possiblyStyleElement}
+ {ele}
+ >
+ )
}
)
diff --git a/yarn.lock b/yarn.lock
index dd247b03e..029d35143 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -23995,6 +23995,23 @@ react-timer-mixin@^0.13.4:
resolved "https://registry.npmjs.org/react-timer-mixin/-/react-timer-mixin-0.13.4.tgz#75a00c3c94c13abe29b43d63b4c65a88fc8264d3"
integrity sha512-4+ow23tp/Tv7hBM5Az5/Be/eKKF7DIvJ09voz5LyHGQaqqz9WV8YMs31eFvcYQs7d451LSg7kDJV70XYN/Ug/Q==
+"react18-dom@npm:react-dom@alpha":
+ version "18.0.0-alpha-327d5c484-20211106"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.0.0-alpha-327d5c484-20211106.tgz#f8855d8e73876b5dbf6545ca02e14d8644a48008"
+ integrity sha512-yI2Kxy4/+nAFYHtC5uVIwXPURABwBhtqbEgjgRa9cITHsmZIO7AqDee0a5nXVSr8wWmtmbN1vSh29aDicuO03A==
+ dependencies:
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+ scheduler "0.21.0-alpha-327d5c484-20211106"
+
+"react18@npm:react@alpha":
+ version "18.0.0-alpha-327d5c484-20211106"
+ resolved "https://registry.yarnpkg.com/react/-/react-18.0.0-alpha-327d5c484-20211106.tgz#f0e29b20b8c371697207a9f7b73a7ab625bee4b7"
+ integrity sha512-CB3JnXquQ9FYkd8IpTlvdpyk+HN1fOUDB7NabXTfbycqqRxuKhtijv7W7F/mbTzPLLMrxV5LwUqrLFc25Zy+UQ==
+ dependencies:
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+
react@16.14.0:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
@@ -25339,6 +25356,14 @@ scheduler@0.19.1, scheduler@^0.19.1:
loose-envify "^1.1.0"
object-assign "^4.1.1"
+scheduler@0.21.0-alpha-327d5c484-20211106:
+ version "0.21.0-alpha-327d5c484-20211106"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.21.0-alpha-327d5c484-20211106.tgz#16340fd4c7387ff58ad897b64715c0e3098e9489"
+ integrity sha512-VzlBG9d8m/2GwzXMjh7FoV6lpv/vOKdUYFCw3FCVY2aP/lN4oJkKwOlf4on2vEk855ckx+s3bR4eJLrlD7kZSw==
+ dependencies:
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+
scheduler@^0.13.6:
version "0.13.6"
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889"