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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving support for React 18 :) #2524

Closed
sanjaiyan-dev opened this issue Oct 26, 2021 · 11 comments
Closed

Improving support for React 18 :) #2524

sanjaiyan-dev opened this issue Oct 26, 2021 · 11 comments

Comments

@sanjaiyan-dev
Copy link

sanjaiyan-dev commented Oct 26, 2021

Improving the support for new React 18

Hi , I am requesting this feature after viewing it on vercel's (maintainers of Next js) styled jsx library, for more info please take a look at their discussion about it 馃憤


We can add useInsertionEffect for better hydration & concurrent support 馃挴 (for more info about useInsertionEffect click here )

  • But this only support in React 18 so we need to implement fallback like below
    const useInsertionEffect = React.useInsertionEffect || React.useLayoutEffect
    (inspired by Next Js)

I am extremely sorry if I told or made any mistakes 馃槍

@sanjaiyan-dev
Copy link
Author

// @flow
import * as React from 'react'
import { withEmotionCache } from './context'
import { ThemeContext } from './theming'
import { insertStyles } from '@emotion/utils'
import { isBrowser } from './utils'

import { StyleSheet } from '@emotion/sheet'
import { serializeStyles } from '@emotion/serialize'

type Styles = Object | Array<Object>

type GlobalProps = {
  +styles: Styles | (Object => Styles)
}

let warnedAboutCssPropForGlobal = false

const useInsertionLayout = React.useInsertionEffect || React.useLayoutEffect

// maintain place over rerenders.
// initial render from browser, insertBefore context.sheet.tags[0] or if a style hasn't been inserted there yet, appendChild
// initial client-side render from SSR, use place of hydrating tag

export let Global: React.AbstractComponent<GlobalProps> =
  /* #__PURE__ */ withEmotionCache((props: GlobalProps, cache) => {
    if (
      process.env.NODE_ENV !== 'production' &&
      !warnedAboutCssPropForGlobal && // check for className as well since the user is
      // probably using the custom createElement which
      // means it will be turned into a className prop
      // $FlowFixMe I don't really want to add it to the type since it shouldn't be used
      (props.className || props.css)
    ) {
      console.error(
        "It looks like you're using the css prop on Global, did you mean to use the styles prop instead?"
      )
      warnedAboutCssPropForGlobal = true
    }
    let styles = props.styles

    let serialized = serializeStyles(
      [styles],
      undefined,
      React.useContext(ThemeContext)
    )

    if (!isBrowser) {
      let serializedNames = serialized.name
      let serializedStyles = serialized.styles
      let next = serialized.next
      while (next !== undefined) {
        serializedNames += ' ' + next.name
        serializedStyles += next.styles
        next = next.next
      }

      let shouldCache = cache.compat === true

      let rules = cache.insert(
        ``,
        { name: serializedNames, styles: serializedStyles },
        cache.sheet,
        shouldCache
      )

      if (shouldCache) {
        return null
      }

      return (
        <style
          {...{
            [`data-emotion`]: `${cache.key}-global ${serializedNames}`,
            dangerouslySetInnerHTML: { __html: rules },
            nonce: cache.sheet.nonce
          }}
        />
      )
    }

    // yes, i know these hooks are used conditionally
    // but it is based on a constant that will never change at runtime
    // it's effectively like having two implementations and switching them out
    // so it's not actually breaking anything

    let sheetRef = React.useRef()

    useInsertionLayout(() => {
      const key = `${cache.key}-global`

      let sheet = new StyleSheet({
        key,
        nonce: cache.sheet.nonce,
        container: cache.sheet.container,
        speedy: cache.sheet.isSpeedy
      })
      let rehydrating = false
      // $FlowFixMe
      let node: HTMLStyleElement | null = document.querySelector(
        `style[data-emotion="${key} ${serialized.name}"]`
      )
      if (cache.sheet.tags.length) {
        sheet.before = cache.sheet.tags[0]
      }
      if (node !== null) {
        rehydrating = true
        // clear the hash so this node won't be recognizable as rehydratable by other <Global/>s
        node.setAttribute('data-emotion', key)
        sheet.hydrate([node])
      }
      sheetRef.current = [sheet, rehydrating]
      return () => {
        sheet.flush()
      }
    }, [cache])

    useInsertionLayout(() => {
      let sheetRefCurrent = (sheetRef.current: any)
      let [sheet, rehydrating] = sheetRefCurrent
      if (rehydrating) {
        sheetRefCurrent[1] = false
        return
      }
      if (serialized.next !== undefined) {
        // insert keyframes
        insertStyles(cache, serialized.next, true)
      }

      if (sheet.tags.length) {
        // if this doesn't exist then it will be null so the style element will be appended
        let element = sheet.tags[sheet.tags.length - 1].nextElementSibling
        sheet.before = ((element: any): Element | null)
        sheet.flush()
      }
      cache.insert(``, serialized, sheet, false)
    }, [cache, serialized.name])

    return null
  })

if (process.env.NODE_ENV !== 'production') {
  Global.displayName = 'EmotionGlobal'
}

This just my suggestion extremely sorry if I made mistake 馃憤

@Andarist
Copy link
Member

We are keeping our eye on the issue - I'm participating in the related discussion in the React 18 Working Group and we are going to brainstorm this next week with Mitchell.

@miracle2k
Copy link

miracle2k commented Nov 4, 2021

Here is a repo that shows the current failure when trying to use emotion with next.js 18 / concurrent mode:

https://github.com/miracle2k/next-rsc-demo

TypeError: Cannot read properties of null (reading 'key')
    at insertStyles (webpack://_N_E/./node_modules/@emotion/utils/dist/emotion-utils.browser.esm.js?:19:25)
    at eval (webpack://_N_E/./node_modules/@emotion/react/dist/emotion-element-99289b21.browser.esm.js?:194:75)
    at eval (webpack://_N_E/./node_modules/@emotion/react/dist/emotion-element-99289b21.browser.esm.js?:56:12)
    at renderWithHooks (webpack://_N_E/./node_modules/react-dom/cjs/react-dom-server.browser.development.js?:5212:16)
    at renderForwardRef (webpack://_N_E/./node_modules/react-dom/cjs/react-dom-server.browser.development.js?:5381:18)
    at renderElement (webpack://_N_E/./node_modules/react-dom/cjs/react-dom-server.browser.development.js?:5526:11)
    at renderNodeDestructive (webpack://_N_E/./node_modules/react-dom/cjs/react-dom-server.browser.development.js?:5606:11)
    at renderNode (webpack://_N_E/./node_modules/react-dom/cjs/react-dom-server.browser.development.js?:5722:12)
    at renderHostElement (webpack://_N_E/./node_modules/react-dom/cjs/react-dom-server.browser.development.js?:5197:3)
    at renderElement (webpack://_N_E/./node_modules/react-dom/cjs/react-dom-server.browser.development.js?:5473:5)
error - unhandledRejection: TypeError: Cannot read properties of null (reading 'key')

In addition, there is a different error if you try to use it as part of a server component.

@Andarist
Copy link
Member

Andarist commented Nov 6, 2021

@miracle2k I've tried running this repro and it doesn't seem to work. I end up with:

error - Error: ENOENT: no such file or directory, open '/next-rsc-demo/.next/server/middleware-build-manifest.js'

@sanjaiyan-dev
Copy link
Author

@miracle2k Try adding custom _document.js & _app.js for ssr or ssg support of emotion css :)

This is Mui v5 repo to get custom _document.js and _app.js
Mui v5 uses Emotion CSS (so downloading mui v5 is not compulsory)

Hope it works 馃憤

@miracle2k
Copy link

miracle2k commented Nov 28, 2021

@SanjaiyanUnofficial I'm not sure if this would be sufficient, as next.js seems to disallow the use of getInitialProps on Document if concurrent mode is on. But I will try to look into it. A related report about emotion compatibility on the next.js side is:

vercel/next.js#31678

@Andarist
Copy link
Member

@miracle2k thank you for bringing this to my attention. I鈥檝e commented in the linked thread

@sanjaiyan-dev
Copy link
Author

sanjaiyan-dev commented Dec 3, 2021

@SanjaiyanUnofficial I'm not sure if this would be sufficient, as next.js seems to disallow the use of getInitialProps on Document if concurrent mode is on. But I will try to look into it. A related report about emotion compatibility on the next.js side is:

vercel/next.js#31678

Ohh kk and Extremely sorry :)

@sanjaiyan-dev
Copy link
Author

Successfully issue solved from version 11.8.1 馃敟

@chaudharykiran
Copy link

Is it working?

Below is my packages:

@emotion/cache@11.7.1
@emotion/react@11.9.0
@emotion/server@11.4.0
@emotion/styled@11.8.1

All the packages are in latest version but I am still getting this error.
TypeError: Cannot read property 'useContext' of null

Anyhelp will be great.

@Andarist
Copy link
Member

To get help you need to provide a repro case

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants