├── .gitignore ├── .npmrc ├── README.md ├── components ├── IncrementCounter.js ├── Page.js └── Users.js ├── package.json ├── pages ├── _app.js ├── index.js └── other.js ├── redux.md └── store ├── counter ├── action.js └── reducer.js ├── store.js └── users ├── action.js └── reducer.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | save-exact=true 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # With Redux Wrapper Example 3 | 4 | Usually splitting your app state into `pages` feels natural but sometimes you'll want to have global state for your app. 5 | The concept of global state caters to both client side and server side stores with the help of a master reducer. 6 | This is an example on how you can use redux that also works with our universal rendering approach. This is just a way you 7 | can do it but it's not the only one. Please find other methods under the official documentation of next-redux-wrapper. 8 | Docs: https://github.com/kirill-konshin/next-redux-wrapper 9 | 10 | ## How to use 11 | 12 | Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: 13 | 14 | ```bash 15 | git clone https://github.com/mayank7924/nextjs-with-redux 16 | cd nextjs-with-redux 17 | npm install 18 | for running development server: npm run dev 19 | for running prod like server: npm run build, npm run start 20 | ``` 21 | 22 | Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). 23 | 24 | ## NOTE: There are three projects present in this repo in separate branches 25 | main: redux with Next.JS 26 | |-starter-project: starter Next.JS app 27 | |-redux-toolkit: redux-toolkit wuth Next.JS 28 | 29 | 30 | -------------------------------------------------------------------------------- /components/IncrementCounter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector, useDispatch } from 'react-redux' 3 | 4 | const IncrementCounter = () => { 5 | const dispatch = useDispatch() 6 | const counter = useSelector((state) => state.counter.count) 7 | return ( 8 |
9 |

10 | Counter: {counter} 11 |

12 | 13 |
14 | ) 15 | } 16 | 17 | export default IncrementCounter 18 | -------------------------------------------------------------------------------- /components/Page.js: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import Users from "./Users"; 3 | import AddCount from "./IncrementCounter"; 4 | 5 | const Page = (props) => { 6 | return ( 7 |
8 |

{props.title}

9 | 10 | 11 |
12 | 17 |
18 | ); 19 | }; 20 | 21 | export default Page; 22 | -------------------------------------------------------------------------------- /components/Users.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { addUser } from "../store/users/action"; 4 | 5 | export default function Clock() { 6 | const dispatch = useDispatch(); 7 | const { users } = useSelector((state) => state.users); 8 | const [name, setName] = useState(""); 9 | 10 | const addNewUser = () => { 11 | dispatch(addUser(name)); 12 | }; 13 | 14 | const handleChange = (event) => { 15 | setName(event.target.value); 16 | }; 17 | 18 | return ( 19 |
20 | 21 | 22 | 23 |

User List:

24 |
    25 | {users.map((user) => ( 26 |
  1. {user}
  2. 27 | ))} 28 |
29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "next", 5 | "build": "next build", 6 | "start": "next start" 7 | }, 8 | "dependencies": { 9 | "next": "9.4.1", 10 | "next-redux-wrapper": "^7.0.2", 11 | "react": "^17.0.2", 12 | "react-dom": "^17.0.2", 13 | "react-redux": "7.1.3", 14 | "redux": "4.0.5", 15 | "redux-devtools-extension": "2.13.8", 16 | "redux-thunk": "2.3.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import { wrapper } from '../store/store' 2 | 3 | const App = ({ Component, pageProps }) => { 4 | return 5 | } 6 | 7 | export default wrapper.withRedux(App) 8 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Page from '../components/Page' 2 | import { incrementCounter } from '../store/counter/action' 3 | import { wrapper } from '../store/store' 4 | 5 | const Index = (props) => { 6 | return 7 | } 8 | 9 | export const getStaticProps = wrapper.getStaticProps((store) => () => { 10 | store.dispatch(incrementCounter()) 11 | }) 12 | 13 | export default Index 14 | -------------------------------------------------------------------------------- /pages/other.js: -------------------------------------------------------------------------------- 1 | import Page from "../components/Page"; 2 | import { wrapper } from "../store/store"; 3 | import { incrementCounter } from "../store/counter/action"; 4 | import { addUser } from '../store/users/action'; 5 | 6 | const Other = (props) => { 7 | return ; 8 | }; 9 | 10 | export const getServerSideProps = wrapper.getServerSideProps((store) => async () => { 11 | store.dispatch(incrementCounter()); 12 | 13 | const response = await fetch(`https://reqres.in/api/users/${Math.floor(Math.random() * (10) + 1)}`); 14 | const {data} = await response.json(); 15 | store.dispatch(addUser(`${data.first_name} ${data.last_name}`)) 16 | }); 17 | 18 | export default Other; 19 | -------------------------------------------------------------------------------- /redux.md: -------------------------------------------------------------------------------- 1 | - Redux is a state management library 2 | - In React you only have client side redux stores 3 | - In Next.JS you can also fetch data on server side during SSR 4 | - We create a new server side redux store in such cases and update it with fetched data 5 | - HYDRATE action from next-redux-wrapper is triggered in such cases 6 | - While handling HYDRATE action we need to apply the new store values to the client store -------------------------------------------------------------------------------- /store/counter/action.js: -------------------------------------------------------------------------------- 1 | export const counterActionTypes = { 2 | INCREMENT: "INCREMENT", 3 | }; 4 | 5 | export const incrementCounter = () => { 6 | return { type: counterActionTypes.INCREMENT }; 7 | }; 8 | -------------------------------------------------------------------------------- /store/counter/reducer.js: -------------------------------------------------------------------------------- 1 | import { counterActionTypes } from "./action"; 2 | 3 | const counterInitialState = { 4 | count: 0, 5 | }; 6 | 7 | export default function reducer(state = counterInitialState, action) { 8 | switch (action.type) { 9 | case counterActionTypes.INCREMENT: 10 | return { ...state, count: state.count + 1 }; 11 | default: 12 | return state; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /store/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, combineReducers } from 'redux' 2 | import { HYDRATE, createWrapper } from 'next-redux-wrapper' 3 | import { composeWithDevTools } from 'redux-devtools-extension' 4 | import users from './users/reducer' 5 | import counter from './counter/reducer' 6 | 7 | const combinedReducer = combineReducers({ 8 | counter, 9 | users, 10 | }) 11 | 12 | const masterReducer = (state, action) => { 13 | if (action.type === HYDRATE) { 14 | const nextState = { 15 | ...state, 16 | counter: { 17 | count: state.counter.count + action.payload.counter.count 18 | }, 19 | users: { 20 | users: [...new Set([...action.payload.users.users, ...state.users.users])] 21 | } 22 | } 23 | return nextState 24 | } 25 | else { 26 | return combinedReducer(state, action); 27 | } 28 | } 29 | 30 | const initStore = () => { 31 | return createStore(masterReducer, composeWithDevTools( 32 | applyMiddleware() 33 | )) 34 | } 35 | 36 | export const wrapper = createWrapper(initStore) 37 | -------------------------------------------------------------------------------- /store/users/action.js: -------------------------------------------------------------------------------- 1 | export const usersActionTypes = { 2 | ADD_USER: "ADD_USER", 3 | }; 4 | 5 | export const addUser = (newUser) => { 6 | return { type: usersActionTypes.ADD_USER, user: newUser }; 7 | }; 8 | -------------------------------------------------------------------------------- /store/users/reducer.js: -------------------------------------------------------------------------------- 1 | import { usersActionTypes } from "./action"; 2 | 3 | const usersInitialState = { 4 | users: ["John Doe", "Mary Jane"] 5 | }; 6 | 7 | export default function reducer(state = usersInitialState, action) { 8 | switch (action.type) { 9 | case usersActionTypes.ADD_USER: { 10 | return { ...state, users: [...state.users, action.user] }; 11 | } 12 | default: 13 | return state; 14 | } 15 | } 16 | --------------------------------------------------------------------------------