rt2zz / redux-persist

persist and rehydrate a redux store
MIT License
12.97k stars 867 forks source link

Error: redux-persist: persist timed out for persist key [...] #997

Closed matheus-rosin closed 5 years ago

matheus-rosin commented 5 years ago

I am getting the following error when pages is being first loaded:

Error: redux-persist: persist timed out for persist key "..."

I have scrutinized my code a thousand times and can't found nothing wrong:

// store.js

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { persistReducer, persistStore } from 'redux-persist';
import * as storage from 'localforage';
import createEncryptor from 'redux-persist-transform-encrypt';
import { createLogger } from 'redux-logger';

import rootReducer from './reducers';
import * as initialState from './initialState';

const encryptor = createEncryptor({
  secretKey: process.env.reduxPersistEncryptorSecretKey,
});

const persistedReducer = persistReducer({
  key: 'some-key',
  storage,
  transforms: [encryptor],
  blacklist: ['_persist'],
}, rootReducer);

const middlewares = [thunk];
if (process.env.NODE_ENV !== 'production') {
  middlewares.push(createLogger());
}

// this config is because I use Next.js and need to have store and persistor on _app.js
export function initializeStore(state = initialState) {
  let store = createStore(
    persistedReducer,
    state,
    applyMiddleware(...middlewares),
  );
  let persistor = persistStore(store);

  return { store, persistor };
}
// _app.js
// I'm simplifying things here, but all has to do with state is left unchanged

import App, { Container } from 'next/app';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import stateHOC from '~/state/stateHOC';

import LoadingScreen from '~/components/_app/loadingScreen';

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    let pageProps = {};

    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx);
    }

    return {
      pageProps,
    };
  }

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

    return (
      <Container>
        <Provider store={reduxStore}>
          <PersistGate loading={<LoadingScreen />} persistor={reduxPersistor}>
            <Component
              {...pageProps}
              pageContext={pageContext}
            />
          </PersistGate>
        </Provider>
      </Container>
    );
  }
}

export default stateHOC(MyApp);
// stateHOC.js

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

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

function getOrCreateStore(state) {
  if (isServer) return initializeStore(state);

  if (!window[__NEXT_REDUX__]) {
    window[__NEXT_REDUX__] = initializeStore(state);
  }
  return window[__NEXT_REDUX__];
}

export default (App) => {
  return class extends Component {
    static async getInitialProps(appContext) {
      const { store, persistor } = getOrCreateStore();
      appContext.ctx.reduxStore = store;
      appContext.ctx.reduxPersistor = persistor;

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

      return {
        ...appProps,
        initialState: store.getState(),
      };
    }

    constructor(props) {
      super(props);
      let { store, persistor } = getOrCreateStore(props.initialState);
      this.reduxStore = store;
      this.reduxPersistor = persistor;
    }

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

Everything works fine - I mean, after page has loaded even with error, changes made to state are persisted to IndexedDB, data are encrypted etc.; and if I refresh the page, even with error, I can see the state saved on IndexedDB is still there, untouched. But no matter what I do, I am getting the same error since the first time I ran npm run dev (which calls next).

Any help? Pleaaase :)

matheus-rosin commented 5 years ago

I tried something based on this and got this thing working. Here is how it looks like:

// store.js

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { createLogger } from 'redux-logger';

import rootReducer from './reducers';
import * as initialState from './initialState';

const middlewares = [thunk];
if (process.env.NODE_ENV !== 'production') {
  middlewares.push(createLogger());
}

function makeConfiguredStore(reducer, initialState) {
  return createStore(
    reducer,
    initialState,
    applyMiddleware(...middlewares),
  );
}

export function initializeStore(initial = initialState, isServer) {
  if (isServer) {
    // if is server, return just store -- not even import redux-persist stuff
    return {
      store: makeConfiguredStore(rootReducer, initial),
    };
  }

  const { persistReducer, persistStore } = require('redux-persist');
  const createEncryptor = require('redux-persist-transform-encrypt').default;
  const storage = require('localforage');

  const encryptor = createEncryptor({
    secretKey: process.env.reduxPersistEncryptorSecretKey,
  });

  const persistedReducer = persistReducer({
    key: 'some-key',
    storage,
    transforms: [encryptor],
    blacklist: ['_persist'],
  }, rootReducer);

  let store = makeConfiguredStore(persistedReducer, initial);
  let persistor = persistStore(store);

  return { store, persistor };
}
// stateHOC.js

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

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

function getOrCreateStore(state) {
  // note line below
  if (isServer) return initializeStore(state, isServer);

  if (!window[__NEXT_REDUX__]) {
    // note line below
    window[__NEXT_REDUX__] = initializeStore(state, isServer);
  }
  return window[__NEXT_REDUX__];
}

export default (App) => {
  return class extends Component {
    static async getInitialProps(appContext) {
      const { store, persistor } = getOrCreateStore();
      appContext.ctx.reduxStore = store;
      appContext.ctx.reduxPersistor = persistor;

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

      return {
        ...appProps,
        initialState: store.getState(),
      };
    }

    constructor(props) {
      super(props);
      let { store, persistor } = getOrCreateStore(props.initialState);
      this.reduxStore = store;
      this.reduxPersistor = persistor;
    }

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

This skips redux-persist on server; and somehow, it just works.