Next.js: with-redux-persist SSR issue

Created on 5 Aug 2019  路  12Comments  路  Source: vercel/next.js

Example bug report

Example name

with-redux-persist

Describe the bug

A clear and concise description of what the bug is.
Implementing with-redux-persist leads to HTML elements missing from the page source.

To Reproduce

  1. In _app.js
  2. Wrap the components with <Provider> and <PersistGate>

_app.js

render() {
    const { Component, pageProps, reduxStore } = this.props;

    return (
      <Container>
        <AppPageHead />
        <GlobalStyle />
        <Provider store={reduxStore}>
          <PersistGate loading={null} persistor={this.persistor}>
            <Layout>
              <Component {...pageProps} {...this.state} />
            </Layout>
          </PersistGate>
        </Provider>
      </Container>
    );
}

test.js

import React from 'react';

export default () => <div> Hello world</div>;

with-redux-store.js

import React from 'react';
import { initializeStore } from '../store';

const isServer = typeof window === 'undefined';
const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__';

function getOrCreateStore(initialState) {
  // Always make a new store if server, otherwise state is shared between requests
  if (isServer) {
    return initializeStore(initialState);
  }

  // Create store if unavailable on the client and set it on the window object
  if (!window[__NEXT_REDUX_STORE__]) {
    window[__NEXT_REDUX_STORE__] = initializeStore(initialState);
  }
  return window[__NEXT_REDUX_STORE__];
}

export default App =>
  class AppWithRedux extends React.Component {
    static async getInitialProps(appContext) {
      // Get or Create the store with `undefined` as initialState
      // This allows you to set a custom default initialState
      const reduxStore = getOrCreateStore();

      // Provide the store to getInitialProps of pages
      appContext.ctx.reduxStore = reduxStore;

      let appProps = {};
      if (typeof App.getInitialProps === 'function') {
        appProps = await App.getInitialProps(appContext);
      }

      return {
        ...appProps,
        initialReduxState: reduxStore.getState(),
      };
    }

    constructor(props) {
      super(props);
      this.reduxStore = getOrCreateStore(props.initialReduxState);
    }

    render() {
      return <App {...this.props} reduxStore={this.reduxStore} />;
    }
  };

Expected behavior

  1. SSR to work as expected

Screenshots

Before
Screen Shot 2019-08-05 at 10 51 38 AM

After
Screen Shot 2019-08-05 at 10 52 52 AM

System information

  • OS: macOS
  • Browser chrome
  • Version of Next.js: 9.0.2

Most helpful comment

Pretty disappointed to find this closed without a clear response.

All 12 comments

What was the solution to this issue?

Running into this issue as well. Any fix?

@ghanshyam-1805 @andrepcg
What I did was to return the same thing () in loading, but I still have doubt in the implementation

```JavaScript
render() {
const { Component, pageProps, reduxStore } = this.props;

return (
  <Container>
    <AppPageHead />
    <GlobalStyle />
    <Provider store={reduxStore}>
      <PersistGate loading={<Layout>
          <Component {...pageProps} {...this.state} />
        </Layout>} persistor={this.persistor}>
        <Layout>
          <Component {...pageProps} {...this.state} />
        </Layout>
      </PersistGate>
    </Provider>
  </Container>
);

}

Any solution to this issue?
i'm also having the same problem, when i return the same thing in loading it fixes the page source problem but because i'm using Material-ui, the styles of Material-ui are not being rendered on server side. the styles are being rendered fine on server side when i remove PersistGate

I also have the same problem when using Material-UI

Pretty disappointed to find this closed without a clear response.

return process.browser ? (
        <PersistGate persistor={store.__persistor} loading={<div>Loading</div>}>
            <ToastProvider>
                <Component {...pageProps} />
            </ToastProvider>
        </PersistGate>
    ) : (
            <Provider store={store}>
                <ToastProvider>
                    <Component {...pageProps} />
                </ToastProvider>
            </Provider>
        );

That solved the problem for me ...

@jeffersoncustodio820 Your solution is good, but this causes ReactDOM.hydrate to throw an HTML mismatch error.

Here, this is my solution either:

This is able to work on the server and client side. Also, since it works the same way on both sides, so we don't annoy the hydrate function for HTML mismatch error.

<PersistGate loading={null} persistor={persistor}>
    {() => (
        <ThemeProvider theme={{}}>
            <EverythingElse />
        </ThemeProvider>
    )}
</PersistGate>

So, how?

Because, as you can see below, PersistGate defines this.state.bootstrapped's value while componentDidMount work. However, componentDidMount doesn't work on the server-side. Also, it means you can not use redux-persist on the server-side.

  componentDidMount() {
    this._unsubscribe = this.props.persistor.subscribe(
      this.handlePersistorState
    )
    this.handlePersistorState()
  }

  handlePersistorState = () => {
    const { persistor } = this.props
    let { bootstrapped } = persistor.getState()
    if (bootstrapped) {
      if (this.props.onBeforeLift) {
        Promise.resolve(this.props.onBeforeLift())
          .finally(() => this.setState({ bootstrapped: true }))
      } else {
        this.setState({ bootstrapped: true })
      }
      this._unsubscribe && this._unsubscribe()
    }
  }

If we passed a function as a child, we can return our component directly according to the code below. In this scenario, the status of this.state.bootstrapped is not important.

  render() {
    if (process.env.NODE_ENV !== 'production') {
      if (typeof this.props.children === 'function' && this.props.loading)
        console.error(
          'redux-persist: PersistGate expects either a function child or loading prop, but not both. The loading prop will be ignored.'
        )
    }
    if (typeof this.props.children === 'function') {
      return this.props.children(this.state.bootstrapped)
    }

    return this.state.bootstrapped ? this.props.children : this.props.loading
  }

I am still stuck.
I did same as @jeffersoncustodio820 suggest.
But on Facebook sharing debug tool and google Seo tool , I didn't see my meta tags.
there i call Api from which i get result and then i put elements from result in the i put them in there respective meta tags.

@nuriun 's solution is useful for me.

Had the same issue and @nuriun 's solution worked for me.

The fix @vizFlux gave follows the with-redux-persist example.

@nuriun can you provide a full example of your implementation? i cant't figure out how you wrap your _app component. Thanks a lot!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

formula349 picture formula349  路  3Comments

flybayer picture flybayer  路  3Comments

swrdfish picture swrdfish  路  3Comments

sospedra picture sospedra  路  3Comments

rauchg picture rauchg  路  3Comments