Gatsby: Warn when user has multiple plugins implementing APIs that should only have 1

Created on 3 Sep 2017  路  10Comments  路  Source: gatsbyjs/gatsby

A community member asked on Discord why their Redux code wasn't working. After looking at their site for a bit I noticed they were also using gatsby-plugin-styled-components. Both their redux plugin & styled components implemented in their gatsby-ssr.js the API replaceRenderer鈥攚hich there can only be one of.

We should when loading plugins check for cases like this and print out a warning in the console that things won't work as expected and suggest workarounds e.g. in this case, pull the styled-component SSR code into their custom redux code.

documentation

Most helpful comment

Whoops, this appears to work only in development mode:

import { ServerStyleSheet, StyleSheetManager } from 'styled-components'

import { Provider } from 'react-redux'
import React from 'react'
import initStore from './src/shared/store/initRedux'
import { renderToString } from 'react-dom/server'

exports.replaceRenderer = ({
  bodyComponent,
  replaceBodyHTMLString,
  setHeadComponents
}) => {
  const sheet = new ServerStyleSheet()
  const store = initStore()

  const app = () => (
    <Provider store={store}>
      <StyleSheetManager sheet={sheet.instance}>
        {bodyComponent}
      </StyleSheetManager>
    </Provider>
  )

  const body = renderToString(app)

  replaceBodyHTMLString(body)

  setHeadComponents([sheet.getStyleElement()])
}

You will get react minified error 46, which is "renderToString: you must pass a valid React Element"

so I got rid of the consts and can confirm this is working in both prod / dev

import { ServerStyleSheet, StyleSheetManager } from 'styled-components'

import { Provider } from 'react-redux'
import React from 'react'
import initStore from './src/shared/store/initRedux'
import { renderToString } from 'react-dom/server'

exports.replaceRenderer = ({
  bodyComponent,
  replaceBodyHTMLString,
  setHeadComponents
}) => {
  const sheet = new ServerStyleSheet()
  const store = initStore()

  replaceBodyHTMLString(
    renderToString(
      <Provider store={store}>
        <StyleSheetManager sheet={sheet.instance}>
          {bodyComponent}
        </StyleSheetManager>
      </Provider>
    )
  )

  setHeadComponents([sheet.getStyleElement()])
}

All 10 comments

After combining the two replaceRenderer files, (removing the styled-components plugin , adding the code to the redux ssr code) ... I ended up with this file

import { ServerStyleSheet, StyleSheetManager } from 'styled-components'

import { Provider } from 'react-redux'
import React from 'react'
import initStore from './src/shared/store/initRedux'
import { renderToString } from 'react-dom/server'

exports.replaceRenderer = ({
  bodyComponent,
  replaceBodyHTMLString,
  setHeadComponents
}) => {
  const sheet = new ServerStyleSheet()
  const store = initStore()

  const app = () => (
    <Provider store={store}>
      <StyleSheetManager sheet={sheet.instance}>
        {bodyComponent}
      </StyleSheetManager>
    </Provider>
  )

  const body = renderToString(app)

  replaceBodyHTMLString(body)

  setHeadComponents([sheet.getStyleElement()])

  return
}

by all means it looks like it should have worked, but instead I got anywhere between 12-24 errors along the lines of:

component---src-layouts-index-js-36c5da770483ba68dacf.js Failed to load resource: the server responded with a status of 404 (Not Found)
component---src-pages-index-js-87d1c17ad13dae91a1d2.js Failed to load resource: the server responded with a status of 404 (Not Found)
path---index-a0e39f21c11f6a62c5ab.js Failed to load resource: the server responded with a status of 404 (Not Found)
component---src-layouts-index-js-36c5da770483ba68dacf.js Failed to load resource: the server responded with a status of 404 (Not Found)
component---src-pages-index-js-87d1c17ad13dae91a1d2.js Failed to load resource: the server responded with a status of 404 (Not Found)
path---index-a0e39f21c11f6a62c5ab.js Failed to load resource: the server responded with a status of 404 (Not Found)
component---src-pages-index-js-87d1c17ad13dae91a1d2.js Failed to load resource: the server responded with a status of 404 (Not Found)
path---index-a0e39f21c11f6a62c5ab.js Failed to load resource: the server responded with a status of 404 (Not Found)
component---src-layouts-index-js-36c5da770483ba68dacf.js Failed to load resource: the server responded with a status of 404 (Not Found)
app-47a21a6fc641308aca59.js:4142 bundle loading error true
app-47a21a6fc641308aca59.js:933 Loading the component for / failed
app-47a21a6fc641308aca59.js:537 bundle loading error true
app-47a21a6fc641308aca59.js:940 Loading the JSON for / failed
app-47a21a6fc641308aca59.js:600 bundle loading error true
app-47a21a6fc641308aca59.js:948 Loading the Layout for / failed
component---src-pages-index-js-87d1c17ad13dae91a1d2.js Failed to load resource: the server responded with a status of 404 (Not Found)
path---index-a0e39f21c11f6a62c5ab.js Failed to load resource: the server responded with a status of 404 (Not Found)
component---src-layouts-index-js-36c5da770483ba68dacf.js Failed to load resource: the server responded with a status of 404 (Not Found)

Running gatsby build:

Minified React error #46; visit http://facebook.github.io/react  /docs/error-decoder.html?invariant=46 for the full message or use the non-minified dev environment for   full errors and additional helpful warnings.

    - render-page.js:4936 reactProdInvariant
      render-page.js:4936:16

    - render-page.js:19988 renderToString
      render-page.js:19988:122

    - render-page.js:42261 Object.exports.replaceRenderer
      render-page.js:42261:42

    - render-page.js:40928
      render-page.js:40928:39

    - render-page.js:40926 module.exports
      render-page.js:40926:26

    - render-page.js:176 module.exports
      render-page.js:176:31

Whoops, this appears to work only in development mode:

import { ServerStyleSheet, StyleSheetManager } from 'styled-components'

import { Provider } from 'react-redux'
import React from 'react'
import initStore from './src/shared/store/initRedux'
import { renderToString } from 'react-dom/server'

exports.replaceRenderer = ({
  bodyComponent,
  replaceBodyHTMLString,
  setHeadComponents
}) => {
  const sheet = new ServerStyleSheet()
  const store = initStore()

  const app = () => (
    <Provider store={store}>
      <StyleSheetManager sheet={sheet.instance}>
        {bodyComponent}
      </StyleSheetManager>
    </Provider>
  )

  const body = renderToString(app)

  replaceBodyHTMLString(body)

  setHeadComponents([sheet.getStyleElement()])
}

You will get react minified error 46, which is "renderToString: you must pass a valid React Element"

so I got rid of the consts and can confirm this is working in both prod / dev

import { ServerStyleSheet, StyleSheetManager } from 'styled-components'

import { Provider } from 'react-redux'
import React from 'react'
import initStore from './src/shared/store/initRedux'
import { renderToString } from 'react-dom/server'

exports.replaceRenderer = ({
  bodyComponent,
  replaceBodyHTMLString,
  setHeadComponents
}) => {
  const sheet = new ServerStyleSheet()
  const store = initStore()

  replaceBodyHTMLString(
    renderToString(
      <Provider store={store}>
        <StyleSheetManager sheet={sheet.instance}>
          {bodyComponent}
        </StyleSheetManager>
      </Provider>
    )
  )

  setHeadComponents([sheet.getStyleElement()])
}

@akadop Thanks for the workaround! It saved me tons of time!

Thank's a lot @akadop, I didn't understand why it wasn't working on SSR... You're the real mvp 馃憡

This was done in #3889

@akadop It's really strange how removing those constants makes this work. Any specific explanation for it.

@prajapati-parth
i presume it's because setting it as a const is making it a function that returns a react element, and the renderToString function does not take a function as a valid arg

@prajapati-parth

just checked renderToString()

export function renderToString(element: ReactElement<any>): string;

this is flow typed, so passing in an arg that isn't a ReactElement<any> will throw an error. A function returning the component will not meet the criteria.

this would probably work though:

const app = (
    <Provider store={store}>
      <StyleSheetManager sheet={sheet.instance}>
        {bodyComponent}
      </StyleSheetManager>
    </Provider>
  )

  const body = renderToString(app)

  replaceBodyHTMLString(body)

@akadop
That makes sense. Thanks for digging it out.
We actually had got it wrong in the first place itself. I think since renderToString expects an element and since app in your example (https://github.com/gatsbyjs/gatsby/issues/2005#issuecomment-326787567) can be treated as a stateless functional component, replacing

const body = renderToString(app)

with

const body = renderToString(<app />)

would also work.
The modified example would look like

import { ServerStyleSheet, StyleSheetManager } from 'styled-components'

import { Provider } from 'react-redux'
import React from 'react'
import initStore from './src/shared/store/initRedux'
import { renderToString } from 'react-dom/server'

exports.replaceRenderer = ({
  bodyComponent,
  replaceBodyHTMLString,
  setHeadComponents
}) => {
  const sheet = new ServerStyleSheet()
  const store = initStore()

  const app = () => (
    <Provider store={store}>
      <StyleSheetManager sheet={sheet.instance}>
        {bodyComponent}
      </StyleSheetManager>
    </Provider>
  )

  const body = renderToString(<app />)

  replaceBodyHTMLString(body)

  setHeadComponents([sheet.getStyleElement()])
}

yup, that works too! im not sure about performance implications though, since your wrapping it in another react component (even though it's an SFC.. same thing happens internally)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Oppenheimer1 picture Oppenheimer1  路  3Comments

KyleAMathews picture KyleAMathews  路  3Comments

ghost picture ghost  路  3Comments

magicly picture magicly  路  3Comments

brandonmp picture brandonmp  路  3Comments