Redux Configs

Redux helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test.

How to use

Here we use the Redux variant family Redux, react-redux, redux-thunk, redux-persist, next-redux-wrapper for handle all state managements system. The installation method is as follows below.

Step 1: Set up STORE configs

Adding file & code in ./src/lib/redux/store.js. This is configs supported for handle SSR state & CSR state.

import { createStore, applyMiddleware } from 'redux';
import { createWrapper } from 'next-redux-wrapper';
import thunkMiddleware from 'redux-thunk';
import combinedReducer from './reducers';

// BINDING MIDDLEWARE
const bindMiddleware = (middleware) => {
  if (process.env.NODE_ENV !== 'production') {
    const { composeWithDevTools } = require('redux-devtools-extension');
    return composeWithDevTools(applyMiddleware(...middleware));
  }
  return applyMiddleware(...middleware);
};

const makeStore = ({ isServer }) => {
  if (isServer) {
    //If it's on server side, create a store
    return createStore(combinedReducer, bindMiddleware([thunkMiddleware]));
  } else {
    //If it's on client side, create a store which will persist
    const { persistStore, persistReducer } = require('redux-persist');
    const storage = require('redux-persist/lib/storage').default;

    const persistConfig = {
      key: process.env.NEXT_PUBLIC_APP_NAME ? process.env.NEXT_PUBLIC_APP_NAME : 'nextjs',
      whitelist: ['register', 'auth'], // only counter will be persisted, add other reducers if needed
      storage, // if needed, use a safer storage
    };

    const persistedReducer = persistReducer(persistConfig, combinedReducer); // Create a new reducer with our existing reducer

    const store = createStore(persistedReducer, bindMiddleware([thunkMiddleware])); // Creating the store again

    store.__persistor = persistStore(store); // This creates a persistor object & push that persisted object to .__persistor, so that we can avail the persistability feature

    return store;
  }
};

export const wrapper = createWrapper(makeStore);

Step 2: Set up reducers configs

Adding file & code in ./src/lib/redux/reducers.js.

import { combineReducers } from 'redux';

import count from 'store/example-redux/count/reducer';
import tick from 'store/example-redux/tick/reducer';

const combinedReducer = combineReducers({
  count,
  tick,
});

export default combinedReducer;

Step 3: Init actions & reducers

We have 2 examples related to using a reducer. Adding action & reducer in

First we make count reducer. Adding file & code in ./src/store/example-redux/count/action.js & ./src/store/example-redux/count/reducer.js

action.js

export const countActionTypes = {
  ADD: 'ADD',
};

export const addCount = () => (dispatch) => {
  return dispatch({ type: countActionTypes.ADD });
};

reducer.js

import { countActionTypes } from './action';

const countInitialState = {
  count: 0,
};

export default function reducer(state = countInitialState, action) {
  switch (action.type) {
    case countActionTypes.ADD:
      return Object.assign({}, state, {
        count: state.count + 1,
      });
    default:
      return state;
  }
}

Second we make tick reducer. Adding file & code in ./src/store/example-redux/tick/action.js & ./src/store/example-redux/tick/reducer.js

action.js

export const tickActionTypes = {
  TICK: 'TICK',
};

export const serverRenderClock = (isServer) => (dispatch) => {
  return dispatch({
    type: tickActionTypes.TICK,
    light: !isServer,
    ts: Date.now(),
  });
};

export const startClock = () => (dispatch) => {
  return setInterval(() => dispatch({ type: tickActionTypes.TICK, light: true, ts: Date.now() }), 1000);
};

reducer.js

import { tickActionTypes } from './action';

const tickInitialState = {
  lastUpdate: 0,
  light: false,
};

export default function reducer(state = tickInitialState, action) {
  switch (action.type) {
    case tickActionTypes.TICK:
      return Object.assign({}, state, {
        lastUpdate: action.ts,
        light: !!action.light,
      });
    default:
      return state;
  }
}

Step 4: Implement store wrapper

Adding code in ./src/pages/_app.js

// Vendors
import { useStore } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';

// Configs
import { wrapper } from '@/redux/store';

const App = (props) => {
    const store = useStore((state) => state);
    ...
    return (
        ...
        <PersistGate persistor={store.__persistor} loading={null}>
            ...
        </PersistGate>
        ...
    )
};

export default wrapper.withRedux(App);

Step 5: Implement in pages route Next.js

For test SSR mode. Adding file & code in ./src/pages/example-redux/ssr.js

import { useEffect } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Page from '@/hoc/example-redux/Page';
import { addCount } from 'store/example-redux/count/action';
import { wrapper } from '@/redux/store';
import { serverRenderClock, startClock } from 'store/example-redux/tick/action';

const Other = (props) => {
  useEffect(() => {
    const timer = props.startClock();

    return () => {
      clearInterval(timer);
    };
  }, [props]);

  return <Page title="Other Page" linkTo="ssg" />;
};

export const getServerSideProps = wrapper.getServerSideProps(async ({ store }) => {
  store.dispatch(serverRenderClock(true));
  store.dispatch(addCount());
});

const mapDispatchToProps = (dispatch) => {
  return {
    addCount: bindActionCreators(addCount, dispatch),
    startClock: bindActionCreators(startClock, dispatch),
  };
};

export default connect(null, mapDispatchToProps)(Other);

For test CSR or SSG mode. Adding file & code in ./src/pages/example-redux/ssg.js

import { useEffect } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Page from '@/hoc/example-redux/Page';
import { addCount } from 'store/example-redux/count/action';
import { wrapper } from '@/redux/store';
import { serverRenderClock, startClock } from 'store/example-redux/tick/action';

const Index = (props) => {
  useEffect(() => {
    const timer = props.startClock();

    return () => {
      clearInterval(timer);
    };
  }, [props]);

  return <Page title="Index Page" linkTo="ssr" />;
};

export const getStaticProps = wrapper.getStaticProps(async ({ store }) => {
  store.dispatch(serverRenderClock(true));
  store.dispatch(addCount());
});

const mapDispatchToProps = (dispatch) => {
  return {
    addCount: bindActionCreators(addCount, dispatch),
    startClock: bindActionCreators(startClock, dispatch),
  };
};

export default connect(null, mapDispatchToProps)(Index);

Last updated

Was this helpful?