If you're looking for a version for NextJS 5 (for individual pages) use 1.x branch or you can follow these simple upgrade instructions.
- Usage
- How it works
- Document
- Error Pages
- Upgrade
- Use with layout
- Async actions in
getInitialProps
- Custom serialization and deserialization, usage with Immutable.JS
- Usage with Redux Persist
- Resources
npm install next-redux-wrapper@canary --save
Wrapper has to be attached your _app
component (located in /pages
). All other pages may use regular connect
function of react-redux
.
Here is the minimal setup (makeStore
and reducer
usually are located in other files):
// pages/_app.js
import React from "react";
import {createStore} from "redux";
import {Provider} from "react-redux";
import App, {Container} from "next/app";
import withRedux from "next-redux-wrapper";
const reducer = (state = {foo: ''}, action) => {
switch (action.type) {
case 'FOO':
return {...state, foo: action.payload};
default:
return state
}
};
/**
* @param {object} initialState
* @param {boolean} options.isServer indicates whether it is a server side or client side
* @param {Request} options.req NodeJS Request object (not set when client applies initialState from server)
* @param {Request} options.res NodeJS Request object (not set when client applies initialState from server)
* @param {boolean} options.debug User-defined debug mode param
* @param {string} options.storeKey This key will be used to preserve store in global namespace for safe HMR
*/
const makeStore = (initialState, options) => {
return createStore(reducer, initialState);
};
class MyApp extends App {
static async getInitialProps({Component, ctx}) {
// we can dispatch from here too
ctx.store.dispatch({type: 'FOO', payload: 'foo'});
const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
return {pageProps};
}
render() {
const {Component, pageProps, store} = this.props;
return (
<Container>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</Container>
);
}
}
export default withRedux(makeStore)(MyApp);
And then actual page components can be simply connected:
import React, {Component} from "react";
import {connect} from "react-redux";
class Page extends Component {
static getInitialProps({store, isServer, pathname, query}) {
store.dispatch({type: 'FOO', payload: 'foo'}); // component will be able to read from store's state when rendered
return {custom: 'custom'}; // you can pass some custom props to component from here
}
render() {
return (
<div>
<div>Prop from Redux {this.props.foo}</div>
<div>Prop from getInitialProps {this.props.custom}</div>
</div>
)
}
}
export default connect()(Page);
No magic is involved, it auto-creates Redux store when getInitialProps
is called by Next.js and then passes this store
down to React Redux's Provider
, which is used to wrap the original component, also automatically. On the client side
it also takes care of using same store every time, whereas on server new store is created for each request.
The withRedux
function accepts makeStore
as first argument. The makeStore
function will receive initial state and
should return a new instance of Redux store
each time when called, no memoization needed here, it is automatically done
inside the wrapper.
withRedux
also optionally accepts a config object as second paramter:
storeKey
(optional, string) : the key used onwindow
to persist the store on the clientdebug
(optional, boolean) : enable debug loggingserializeState
anddeserializeState
: custom functions for serializing and deserializing the redux state, see Custom serialization and deserialization.
When makeStore
is invoked it is provided with a configuration object along with NextJS page context which includes:
isServer
(boolean):true
if called while on the server rather than the clientreq
(Request): Thenext.js
getInitialProps
contextreq
parameterres
(Request): Thenext.js
getInitialProps
contextreq
parameter
Additional config properties req
and res
are not set when client applies initialState
from server.
Although it is possible to create server or client specific logic in both makeStore
function and getInitialProps
method I highly don't recommend to have different behavior. This may cause errors and checksum mismatches which in turn
will ruin the whole purpose of server rendering.
I don't recommend to use withRedux
in pages_document.js
, Next.JS does not provide
a reliable way to determine the sequence when components will be rendered. So per Next.JS recommendation it is better
to have just data-agnostic things in pages/_document
.
Error pages also can be wrapped the same way as any other pages.
Transition to an error page (pages/_error.js
template) will cause pages/_app.js
to be applied but it is always a
full page transition (not HTML5 pushstate), so client will have store created from scratch using state from the server,
so unless you persist the store on client somehow the resulting previous client state will be ignored.
If your project was using NextJS 5 and Next Redux Wrapper 1.x these instructions will help you to upgrade to latest version.
-
Upgrade NextJS and Wrapper
$ npm install next@6 --save-dev $ npm install next-redux-wrapper@2 --save
-
Replace all usages of
import withRedux from "next-redux-wrapper";
andwithRedux(...)(WrappedComponent)
in all your pages with plain React Reduxconnect
HOC:import {connect} from "react-redux"; export default connect(...)(WrappedComponent);
You also may have to reformat your wrapper object-based config to simple React Redux config.
-
Create the
pages/_app.js
file with the following minimal code:// pages/_app.js import React from 'react' import {Provider} from "react-redux"; import App, {Container} from "next/app"; import withRedux from "next-redux-wrapper"; import {makeStore} from "../components/store"; export default withRedux(makeStore, {debug: true})(class MyApp extends App { static async getInitialProps({Component, ctx}) { return { pageProps: { // Call page-level getInitialProps ...(Component.getInitialProps ? await Component.getInitialProps(ctx) : {}), } }; } render() { const {Component, pageProps, store} = this.props; return ( <Container> <Provider store={store}> <Component {...pageProps} /> </Provider> </Container> ); } });
-
Follow NextJS 6 upgrade instructions for all your components (
props.router
instead ofprops.url
and so on)
That's it, your project should now work same as before.
MyApp
is not connected to Redux by design in order to keep the interface as minimal as possible, you can return
whatever you want from MyApp
's getInitialProps
, or if you need a shared layout just create it and connect
it as
usual, then include it either in the page itself or render in MyApp
like so:
import React from 'react'
import {Provider} from "react-redux";
import App, {Container} from "next/app";
import withRedux from "next-redux-wrapper";
import {makeStore} from "../components/store";
import ConnectedLayout from "../components/Layout";
export default withRedux(makeStore, {debug: true})(class MyApp extends App {
static async getInitialProps({Component, ctx}) {
return {
pageProps: {
// Call page-level getInitialProps
...(Component.getInitialProps ? await Component.getInitialProps(ctx) : {})
}
};
}
render() {
const {Component, pageProps} = this.props;
return (
<Container>
<Provider store={store}>
<ConnectedLayout>
<Component {...pageProps} />
</ConnectedLayout>
</Provider>
</Container>
);
}
});
function someAsyncAction() {
return {
type: 'FOO',
payload: new Promise((res) => { res('foo'); })
}
}
function getInitialProps({store, isServer, pathname, query}) {
// lets create an action using creator
const action = someAsyncAction();
// now the action has to be dispatched
store.dispatch(action);
// once the payload is available we can resume and render the app
return action.payload.then((payload) => {
// you can do something with payload now
return {custom: 'custom'};
});
}
If you are storing complex types such as Immutable.JS or EJSON objecs in your state, a custom serialize and deserialize
handler might be handy to serialize the redux state on the server and derserialize it again on the client. To do so,
provide serializeState
and deserializeState
as config options to withRedux
.
The reason is that initialState
is transferred over the network from server to client as a plain object.
Example of a custom serialization of an Immutable.JS state using json-immutable
:
const {serialize, deserialize} = require('json-immutable');
withRedux(
(initialState, options) => {...}, // makeStore
{
serializeState: state => serialize(state),
deserializeState: state => deserialize(state)
}
);
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).
// lib/redux.js
import logger from 'redux-logger';
import {applyMiddleware, createStore} from 'redux';
const SET_CLIENT_STATE = 'SET_CLIENT_STATE';
export const reducer = (state, {type, payload}) => {
if (type === SET_CLIENT_STATE) {
return {
...state,
fromClient: payload
};
}
return state;
};
const makeConfiguredStore = (reducer, initialState) =>
createStore(reducer, initialState, applyMiddleware(logger));
export const makeStore = (initialState, {isServer, req, debug, storeKey}) => {
if (isServer) {
initialState = initialState || {fromServer: 'foo'};
return makeConfiguredStore(reducer, initialState);
} else {
// we need it only on client side
const {persistStore, persistReducer} = require('redux-persist');
const storage = require('redux-persist/lib/storage').default;
const persistConfig = {
key: 'nextjs',
whitelist: ['fromClient'], // make sure it does not clash with server keys
storage
};
const persistedReducer = persistReducer(persistConfig, reducer);
const store = makeConfiguredStore(persistedReducer, initialState);
store.__persistor = persistStore(store); // Nasty hack
return store;
}
};
export const setClientState = (clientState) => ({
type: SET_CLIENT_STATE,
payload: clientState
});
And then in NextJS _app
page:
// pages/_app.js
import React from "react";
import {Provider} from "react-redux";
import App, {Container} from "next/app";
import withRedux from "next-redux-wrapper";
import {makeStore} from "./lib/redux";
import {PersistGate} from 'redux-persist/integration/react';
export default withRedux(makeStore, {debug: true})(class MyApp extends App {
render() {
const {Component, pageProps, store} = this.props;
return (
<Container>
<Provider store={store}>
<PersistGate persistor={store.__persistor} loading: {<div>Loading</div>}>
<Component {...pageProps} />
</PersistGate>
</Provider>
</Container>
);
}
});
And then in NextJS page:
// pages/index.js
import React from "react";
import {connect} from "react-redux";
export default connect(
(state) => state,
{setClientState}
)(({fromServer, fromClient, setClientState}) => (
<div>
<div>fromServer: {fromServer}</div>
<div>fromClient: {fromClient}</div>
<div><button onClick={e => setClientState('bar')}>Set Client State</button></div>
</div>
));
- next-redux-saga
- How to use with Redux and Redux Saga
- Redux Saga Example: https://gist.github.com/pesakitan22/94b4984140ba0f2c9e52c5289a7d833e.
- Typescript type definitions >
npm install @types/next-redux-wrapper