does anyone have a working example for next.js using next-redux-wrapper and redux-persist?
With redux-persist v4 it is pretty straightforward: example.
Now with redux-persist 5+ it is a nightmare...
Any help or advice is welcomed.
Yup, it looks like I am running into an issue with integrating withRedux and redux-persist 5.
withRedux expects configureStore fn to return store whereas redux-persist wants to output { store, persistor }.
Tried all kinds of things including exporting { persistor } separately outside the configureStore function, but nothing really seems to work properly.
Integrating withRedux with redux-persist doesn't seem to be very difficult. The problem is to get notified when persisted stated is loaded.
Officially, the easiest way to get aware of loaded stated is through PersistGate which requires persistor and there's where the problems begin.
Not only Next.js or withRedux, but every server side rendering approach will try to render this PersistGate component on server side, however persistor it will not be present at that point.
So, it would be necessary to treat both render attempt methods, which I think IT'S A TERRIBLE IDEIA. Something like that:
_PS: I'm not recommending this approach at all._
class App extends Component {
static getInitialProps({ req, res, store }) {
//this method may or may not be called on client
//!res means this code is running on client
if(!res) {
loadPersistedState(store);
}
return {};
}
componentDidMount() {
//this method is only called on client
//save store to global window object
this.persistor = window.store && loadPersistedState(window.store);
}
render() {
return(
<div>
{
this.persistor ? (
//on browser render
<PersistGate persistor={this.persistor}>
<ChildComponent/>
</PersistGate>
) : (
//on server render
<ChildComponent/>
)
}
</div>
)
}
}
return withRedux(initStore, null, null)(App);
const loadPersistedState = store => {
return persistStore(store);
};
//store
const initStore = (_initialState = {}, {isServer, req}) => {
let store;
if (isServer) {
store = createStore(reducers, _initialState);
} else {
const persistedReducer = persistReducer(persistConfig, reducers);
store = createStore(persistedReducer, _initialState);
window.store = store;
}
return store;
}
My conclusion is that It will be very welcome some alternative way (Perhaps It already exists) to get components notified when persisted state is loaded besides PersistGate, something like the npm module redux-storage does.
I have not used next.js so I may not have full context on this, but I wanted to weigh in on a couple of things:
persistStore(store, null, () => {
})
{ persistor, store }. There are a number of ways to work around this, such as calling persistStore in a separate method, i.e.let store = configureStore()
let persistor = configurePersistor(store)
Hope that helps, if changes are needed in redux-persist to support next.js please let me know and we can evaluate their inclusion.
@rt2zz There it is ! I think that would be very time saving if you expose this method on read.md.
I've done a step forward and created a repo with a demo app which shows Next.JS working pretty nice with redux-persist.
https://github.com/nickmarca/examples-redux-persist-next
Unfortunately, People have to download it and yarn install to see it working. But I try to point out the main parts of this integration:
//#store/serverStore.js
import { createStore } from 'redux';
import reducers from "../reducers/index";
export default (initialState, {isServer}) => {
return createStore(reducers, initialState);
}
//#store/clientStore.js
import { createStore } from 'redux';
import reducers from "../reducers/index";
import storage from "localforage";
import {persistStore, persistReducer} from "redux-persist";
import {loadSuccess} from "../actions";
const persistConfig = {
key: 'root',
storage: storage,
};
const persistedReducer = persistReducer(persistConfig, reducers);
export default (initialState) => {
const store = createStore(persistedReducer, initialState);
//You don't need to delay it, I did because I wish to show how to get notified
//when state is rehydrated
setTimeout(() => {
persistStore(store, null, () => {
store.dispatch(loadSuccess());
});
persistStore(store);
}, 3000);
return store;
}
//#store/index.js
import createStoreFromServer from "./serverStore";
import createStoreFromClient from "./clientStore";
const _initialState = {
myData: {},
status: {loaded: false, err: false}
};
export default (initialState = _initialState, props) => {
if(props.isServer) {
return createStoreFromServer(initialState, props)
} else {
return createStoreFromClient(initialState, props);
}
}
//#reducers/statusReducer.js
import {actionTypes} from "../actions";
export default (state = {loaded: false}, action) => {
switch (action.type) {
case actionTypes.DATA_LOAD_SUCCESS:
return {loaded: true};
default:
return state;
}
}
awesome, added basic api docs here: https://github.com/rt2zz/redux-persist/blob/master/README.md#persiststorestore-config-callback
If you think it could use further clarification please PR!
Base on both redux-persist and next-redux-wrapper docs, I ended up with this solution:
let _persistor;
export default Page => withRedux(
(initialState, { isServer }) => {
const { persistor, store } = makeStore(initialState, { isServer })
_persistor = persistor
return store
}, mapStateToProps, mapDispatchToProps
)(class DefaultPage extends React.Component {
render() {
if (!_persistor) return <Loading/>
return (
<PersistGate loading={<Loading/>} persistor={_persistor}>
<App />
</PersistGate>
)
}
}
//store.js
import { createStore } from "redux";
import rootReducer from "./reducer";
import storage from 'redux-persist/lib/storage'
function clientStore(initialState) {
const { persistStore, persistReducer } = require('redux-persist')
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = createStore(persistedReducer, initialState)
const persistor = persistStore(store)
return { store, persistor }
}
export const makeStore = (initialState, { isServer }) => {
const { store, persistor } = isServer ? { store: createStore(rootReducer, initialState) } : clientStore(initialState);
if (module.hot) {
module.hot.accept('./reducer', () => {
console.log('Replacing reducer');
store.replaceReducer(require('./reducer').default);
});
}
return { store, persistor };
};
Not the prettiest solution, but at least I could use PersistGate element to handle the rehydration loading. So far, it seems to work, but if you see any issue with this approach, please let me know. Thanks.
@element6 thanks for posting your approach. It works for server-side routing but when I use Router.push() for client-side routing, nextjs seems to lose access to _persistor and I get the error below. Refreshing the page manually for server-side fetching actually loads up the page... Did anyone solve this issue?
Cannot read property 'subscribe' of undefined
TypeError: Cannot read property 'subscribe' of undefined
at ProxyComponent.componentDidMount (http://localhost:8000/_next/-/page/register.js:29782:48)
at ProxyComponent.wrappedMethod (http://localhost:8000/_next/-/main.js:27589:123)
at commitLifeCycles (http://localhost:8000/_next/-/main.js:20465:24)
at commitAllLifeCycles (http://localhost:8000/_next/-/main.js:21641:9)
at HTMLUnknownElement.callCallback (http://localhost:8000/_next/-/main.js:12237:14)
at Object.invokeGuardedCallbackDev (http://localhost:8000/_next/-/main.js:12276:16)
at invokeGuardedCallback (http://localhost:8000/_next/-/main.js:12133:27)
at commitRoot (http://localhost:8000/_next/-/main.js:21745:9)
at performWorkOnRoot (http://localhost:8000/_next/-/main.js:22712:42)
at performWork (http://localhost:8000/_next/-/main.js:22662:7)
@agraebe yes, I've updated my solution :)
added if (!_persistor) return <Loading/> to render() to make sure _persistor is loaded before rendering <PersistGate ... />
@element6 Am I assuming correctly that you have an update store solution as well that dispatches a redux event and your component is subscribed to that so it updates as soon as _persistor available?
@agraebe I didn't change my store solution. Which redux event are you referring to? I thought everything happens automatically as PersistGate re-hydrates and connect middleware subscribes the component to the store.
My mistake, I didnt subscribe correctly. Works for me now! Thanks for your help, guys!
Author of next-redux-wrapper here.
Honestly, I think that putting a persistence gate is not necessary because server can already send some HTML with some state, so it's better to show it right away and then wait for REHYDRATE action to happen to show additional delta coming from persistence storage. That's why we use Server Side Rendering in a first place.
But, for those who actually want to block the UI while rehydration is happening, here is the solution (still hacky though): https://github.com/kirill-konshin/next-redux-wrapper/blob/master/README.md#usage-with-redux-persist
@kirill-konshin This is perfect, thanks! Quick question - how would I purge the store e.g. on logout from within a page component?
@agraebe You need to create an action for that, dispatch it and clean whatever you want in reducers, the same way as usual. Persistor will remember cleaned up state.
I have a working example with nextJS + next-redux-wrapper and the latest redux-persist in case this helps anyone.
Here are the relevant files. I'm still new to this, so can't explain in detail too much about the inner workings, just that my state is now fully rehydrated when I do a page refresh, where before it would reset my state to the initialState when I refresh the browser and would only show data again if I changed the schema of the state temporarily so that it detected a change. If I'm doing something that is a super bad idea, I'd certainly appreciate any feedback!
Cheers!
_redux/store.js_
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import loggerMiddleware from './middleware/logger';
import { composeWithDevTools } from 'redux-devtools-extension';
import { persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import rootReducer from './reducers';
const persistConfig = {
key: 'root',
storage,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
export default () => {
return createStore(persistedReducer,
composeWithDevTools(
applyMiddleware(
loggerMiddleware,
thunkMiddleware
)
));
};
_pages/_app.js_
import { Component } from 'react';
import { Provider } from 'react-redux';
import App, { Container } from 'next/app';
import withRedux from 'next-redux-wrapper';
import { PersistGate } from 'redux-persist/integration/react';
import { persistStore } from 'redux-persist';
import store from '../redux/store';
class MyApp extends App {
static async getInitialProps ({Component, ctx}) {
return {
pageProps: (Component.getInitialProps ? await Component.getInitialProps(ctx) : {})
};
}
render () {
const {Component, pageProps, store} = this.props;
const persistor = persistStore(store);
return (
<Container>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Component {...pageProps} />
</PersistGate>
</Provider>
</Container>
);
};
}
export default withRedux(store, {debug: true})(MyApp);
_redux/reducers/index.js_
import { combineReducers } from 'redux';
import userReducer from './user';
export default combineReducers({
user: userReducer
});
I use PersistGate in _document.js and it works nicely.
In getInitialProps, you could use
MyDocument.getInitialProps = ctx => {
let pageContext;
let persistor;
const sheet = new ServerStyleSheet()
const page = ctx.renderPage(Component => {
const WrappedComponent = props => {
pageContext = props.pageContext;
persistor = props.persistor;
return sheet.collectStyles(
<PersistGate loading={null} persistor={persistor}>
<Component {...props} />
</PersistGate>
);
};
WrappedComponent.propTypes = {
pageContext: PropTypes.object.isRequired,
};
return WrappedComponent;
});
...
}
To inject persistor to props, in _app.js, you just do
constructor(props) {
super(props);
this.pageContext = getPageContext();
this.persistor = persistStore(props.store);
}
I would like to reset the topic regarding PersistGate. My actual use case is the following:
I'm using [email protected] with [email protected] and [email protected]. I have a page /pages/home.tsx:
export default class HomePage extends React.PureComponent {
static getInitialProps({ store }) {
store.dispatch(someAction());
}
render() {
return (
<Layout>
<DummyContainer />
</Layout>
);
}
}
I need to wait for REHYDRATE action to be dispatched and right after to dispatch someAction. I don't really want to block UI in all other pages therefore I'm quite certain that PersistGate doesn't fit here. Someone has any proposition what could be the way to achieve that?
@karolisgrinkevicius As i see, you can make a hook in the componentDidMount to dispatch a redux action with payload {mounted: true}, then you can use it to dispatch further action after hydration.
Thanks @revskill10. The following code is how I have ended this up:
export default class HomePage extends React.Component {
static async getInitialProps({ isServer, store }) {
// below I use imported helper function to dispatch an action
// asynchronously and wait for selector to return value of true
// for making sure rehydration is already done before we dispatch further action
await dispatchAsync(store, someAction(), selectRehydrationIsDone);
if (!isServer) {
store.dispatch(actionNeededAfterRehydration());
}
}
render() {
return (
<Layout>
<DummyContainer />
</Layout>
);
}
}
Author of
next-redux-wrapperhere.Honestly, I think that putting a persistence gate is not necessary because server can already send _some_ HTML with _some_ state, so it's better to show it right away and then wait for
REHYDRATEaction to happen to show additional delta coming from persistence storage. That's why we use Server Side Rendering in a first place.But, for those who actually want to block the UI while rehydration is happening, here is the solution (still hacky though): https://github.com/kirill-konshin/next-redux-wrapper/blob/master/README.md#usage-with-redux-persist
I'm still having issues with this. Currently getting:
react.js:53 Uncaught TypeError: Cannot read property 'subscribe' of undefined
HI there... First of all thank by the plugin.
I'm trying to implement this feature in Next js
And I have this code...
_app.js
import { Provider } from "react-redux";
import { appWithTranslation } from "../../i18n";
import withRedux from "next-redux-wrapper";
import { makeStore } from "../lib/redux";
import { PersistGate } from "redux-persist/integration/react";
export default withRedux(makeStore, { debug: true })(appWithTranslation(MyApp))
and in page/index.js I have
export default connect(
(state) => state,
{setClientState}
)(({fromClient, setClientState}))(withNamespaces('landing')(Index))
but IM getting an error of setClientState not defined.
Can you help me, please?
Thanks in advance
Carlos Vieira
I use PersistGate in
_document.jsand it works nicely.In
getInitialProps, you could useMyDocument.getInitialProps = ctx => { let pageContext; let persistor; const sheet = new ServerStyleSheet() const page = ctx.renderPage(Component => { const WrappedComponent = props => { pageContext = props.pageContext; persistor = props.persistor; return sheet.collectStyles( <PersistGate loading={null} persistor={persistor}> <Component {...props} /> </PersistGate> ); }; WrappedComponent.propTypes = { pageContext: PropTypes.object.isRequired, }; return WrappedComponent; }); ... }To inject
persistortoprops, in_app.js, you just doconstructor(props) { super(props); this.pageContext = getPageContext(); this.persistor = persistStore(props.store); }
Could @revskill10 share with the community a boilerplate on how to do this?
Because I do not know how you end up having the persistor in the _document.js
I ended up creating a wrapper around the PersistGate component.
PersistGate.jsx
import React from "react";
import {PersistGate as RealPersistGate, PersistGateProps} from "redux-persist/integration/react";
const canUseDom = !!((typeof window !== 'undefined' && window.document && window.document.createElement));
export class PersistGate extends React.PureComponent<PersistGateProps> {
render() {
const {children, loading, persistor} = this.props;
if (canUseDom) return (
<RealPersistGate loading={loading} persistor={persistor}>
{children}
</RealPersistGate>
);
return children;
}
}
I was also facing the same problem. So I created a boilerplate solution for this. Go check the simple counter app example. It is written with the latest hook and other features.
https://github.com/fazlulkarimweb/with-next-redux-wrapper-redux-persist
@fazlulkarimweb added link to your boilerplate, thanks! https://github.com/kirill-konshin/next-redux-wrapper#usage-with-redux-persist
@fazlulkarimweb added link to your boilerplate, thanks! https://github.com/kirill-konshin/next-redux-wrapper#usage-with-redux-persist
That's very kind of you.
Most helpful comment
I have a working example with nextJS + next-redux-wrapper and the latest redux-persist in case this helps anyone.
Here are the relevant files. I'm still new to this, so can't explain in detail too much about the inner workings, just that my state is now fully rehydrated when I do a page refresh, where before it would reset my state to the initialState when I refresh the browser and would only show data again if I changed the schema of the state temporarily so that it detected a change. If I'm doing something that is a super bad idea, I'd certainly appreciate any feedback!
Cheers!
_redux/store.js_
_pages/_app.js_
_redux/reducers/index.js_