├── .eslintignore ├── .gitignore ├── reducers ├── index.js ├── demoReducer.js └── kittensReducer.js ├── next.config.js ├── actions ├── demoActions.js └── kittensActions.js ├── api └── kittens │ └── index.js ├── store.js ├── pages ├── index.js └── about.js ├── .babelrc ├── README.md ├── .eslintrc.json ├── package.json ├── containers ├── about.js └── home.js └── components └── layout.js /.eslintignore: -------------------------------------------------------------------------------- 1 | static/*.js 2 | static/**/*.js 3 | next.config.js -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .next 3 | coverage 4 | node_modules 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import demoString from './demoReducer'; 3 | import kittens from './kittensReducer'; 4 | 5 | export default combineReducers({ 6 | demoString, 7 | kittens 8 | }); -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | 3 | module.exports = { 4 | webpack: function(c) { 5 | if (c.resolve.alias) { 6 | delete c.resolve.alias["react"]; 7 | delete c.resolve.alias["react-dom"]; 8 | } 9 | return c; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /reducers/demoReducer.js: -------------------------------------------------------------------------------- 1 | import { SET_STRING } from '../actions/demoActions'; 2 | 3 | export default function settingString(state: String = '', action: Object) { 4 | switch (action.type) { 5 | case SET_STRING: 6 | return action.theString; 7 | default: 8 | return state; 9 | } 10 | } -------------------------------------------------------------------------------- /reducers/kittensReducer.js: -------------------------------------------------------------------------------- 1 | import { SET_KITTENS } from '../actions/kittensActions'; 2 | 3 | export default function settingKittens(state: Object[] = [], action: Object) { 4 | switch (action.type) { 5 | case SET_KITTENS: 6 | return Object.assign([], ...state, action.kittens); 7 | default: 8 | return state; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /actions/demoActions.js: -------------------------------------------------------------------------------- 1 | export const SET_STRING = 'SET_STRING'; 2 | /** 3 | * sets the string, if none is passed, it will default to the below string. 4 | * @param theString 5 | * @return {{type: string, theString: String}} 6 | */ 7 | export function setString(theString: String = 'the default string') { 8 | return { 9 | type: SET_STRING, 10 | theString 11 | }; 12 | } -------------------------------------------------------------------------------- /api/kittens/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | /** 3 | * Get the response of the api call to get the kitten list. 4 | * If you want to create your api check this repository 5 | * https://github.com/jsantana90/nextjs-express-boilerplate 6 | * @return {AxiosPromise} 7 | */ 8 | function getKittens() { 9 | return axios.get('https://nextjs-express-boilerplate.now.sh/api/kittens'); 10 | } 11 | 12 | export { getKittens }; 13 | -------------------------------------------------------------------------------- /store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunkMiddleware from 'redux-thunk'; 3 | import { composeWithDevTools } from 'redux-devtools-extension'; 4 | import rootReducer from './reducers'; 5 | 6 | export const initStore = (initialState) => { 7 | if (typeof window === 'undefined') { 8 | return createStore( 9 | rootReducer, 10 | initialState, 11 | composeWithDevTools(applyMiddleware(thunkMiddleware)) 12 | ); 13 | } else { 14 | if (!window.store) { 15 | window.store = createStore( 16 | rootReducer, 17 | initialState, 18 | composeWithDevTools(applyMiddleware(thunkMiddleware)) 19 | ); 20 | } 21 | return window.store; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /actions/kittensActions.js: -------------------------------------------------------------------------------- 1 | import { getKittens } from '../api/kittens'; 2 | export const SET_KITTENS = 'SET_KITTENS'; 3 | /** 4 | * Set the kittens(data response) from parameter to redux state. 5 | * @param kittens 6 | * @return {{type: string, kittens: Object}} 7 | */ 8 | export function setKittens(kittens: Object[]) { 9 | return { 10 | type: SET_KITTENS, 11 | kittens 12 | }; 13 | } 14 | 15 | export function getAsyncKittens() { 16 | return async dispatch => { 17 | try { 18 | const queryResponse = await getKittens(); 19 | console.log('QUERY RESPONSE: ', queryResponse.data); 20 | dispatch(setKittens(queryResponse.data)); 21 | } catch (err) { 22 | console.error(err); 23 | } 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import withRedux from 'next-redux-wrapper'; 4 | import { initStore } from '!/store'; 5 | import Home from '!/containers/home'; 6 | /** 7 | * Component to show the home container. 8 | */ 9 | class App extends React.Component { 10 | static getInitialProps({ store, req }) { 11 | const isServer = !!req; 12 | return { initialState: store.getState(), isServer }; 13 | } 14 | 15 | constructor(props) { 16 | super(props); 17 | this.store = initStore(props.initialState, props.isServer); 18 | } 19 | 20 | render() { 21 | return ( 22 | 23 | 24 | 25 | ); 26 | } 27 | } 28 | 29 | export default withRedux(initStore)(App); 30 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "development": { 4 | "presets": ["next/babel"], 5 | "plugins": [ 6 | ["babel-plugin-root-import", [ 7 | { 8 | "rootPathPrefix": "!" 9 | } 10 | ]] 11 | ] 12 | }, 13 | "production": { 14 | "presets": ["next/babel"], 15 | "plugins": [ 16 | ["babel-plugin-root-import", [ 17 | { 18 | "rootPathPrefix": "!" 19 | } 20 | ]] 21 | ] 22 | }, 23 | "test": { 24 | // next/babel does not transpile import/export syntax. 25 | // So, using es2015 in the beginning will fix that. 26 | "presets": ["es2015", "next/babel"], 27 | "plugins": [ 28 | ["babel-plugin-root-import", [ 29 | { 30 | "rootPathPrefix": "!" 31 | } 32 | ]] 33 | ] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Redux example 3 | 4 | ## How to use 5 | 6 | This boilerplate was continued from [zeit official example](https://github.com/zeit/next.js/tree/master/examples/with-redux). 7 | 8 | To use this boilerplate, first clone or download this repo: [https://github.com/johhansantana/nextjs-redux-boilerplate.git]. 9 | 10 | ```bash 11 | git clone https://github.com/jsantana90/nextjs-redux-boilerplate.git *your-project-name* 12 | ``` 13 | 14 | cd into it and run: 15 | 16 | ```bash 17 | npm install 18 | npm run dev 19 | ``` 20 | 21 | Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)) 22 | 23 | ```bash 24 | now 25 | ``` 26 | 27 | ### Live example: 28 | 29 | [https://nextjs-redux-boilerplate.now.sh] 30 | 31 | Live example uses my other repository [https://github.com/johhansantana/nextjs-express-boilerplate] as api which is deployed in `now` as well, go check it out. 32 | -------------------------------------------------------------------------------- /pages/about.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import withRedux from 'next-redux-wrapper'; 4 | import { initStore } from '!/store'; 5 | import About from '!/containers/about'; 6 | import * as kittensActions from '!/actions/kittensActions'; 7 | /** 8 | * Component to show the about component. 9 | */ 10 | class App extends React.Component { 11 | static async getInitialProps({ store, req }) { 12 | const isServer = !!req; 13 | await store.dispatch(kittensActions.getAsyncKittens()); 14 | return { initialState: store.getState(), isServer }; 15 | } 16 | 17 | constructor(props) { 18 | super(props); 19 | this.store = initStore(props.initialState, props.isServer); 20 | } 21 | 22 | render() { 23 | return ( 24 | 25 | 26 | 27 | ); 28 | } 29 | } 30 | 31 | export default withRedux(initStore)(App); 32 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parser": "babel-eslint", 8 | "parserOptions": { 9 | "ecmaFeatures": { 10 | "experimentalObjectRestSpread": true, 11 | "jsx": true 12 | }, 13 | "sourceType": "module" 14 | }, 15 | "plugins": [ 16 | "react" 17 | ], 18 | "rules": { 19 | "react/jsx-uses-vars": 1, 20 | "react/react-in-jsx-scope": 1, 21 | "react/jsx-uses-react": 1, 22 | "max-len": [2, 100], 23 | "no-console": "warn", 24 | "indent": [ 25 | "error", 26 | 2 27 | ], 28 | "linebreak-style": [ 29 | "error", 30 | "unix" 31 | ], 32 | "quotes": [ 33 | "error", 34 | "single" 35 | ], 36 | "semi": [ 37 | "error", 38 | "always" 39 | ] 40 | } 41 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-redux-boilerplate", 3 | "version": "1.3.1", 4 | "scripts": { 5 | "eslint": "eslint ./", 6 | "eslint-fix": "eslint ./ --fix", 7 | "dev": "next", 8 | "build": "next build", 9 | "start": "next start" 10 | }, 11 | "pre-commit": [ 12 | "eslint" 13 | ], 14 | "now": { 15 | "name": "nextjs-redux-boilerplate", 16 | "alias": "nextjs-redux-boilerplate" 17 | }, 18 | "dependencies": { 19 | "axios": "^0.15.3", 20 | "next": "^2.4.3", 21 | "next-redux-wrapper": "^1.1.1", 22 | "prop-types": "^15.5.9", 23 | "react": "^15.4.2", 24 | "react-dom": "^15.4.2", 25 | "react-redux": "^5.0.1", 26 | "redux": "^3.6.0", 27 | "redux-devtools-extension": "^2.13.2", 28 | "redux-thunk": "^2.1.0" 29 | }, 30 | "devDependencies": { 31 | "babel-eslint": "^7.2.3", 32 | "babel-plugin-root-import": "^5.1.0", 33 | "babel-preset-es2015": "^6.22.0", 34 | "eslint": "^3.19.0", 35 | "eslint-plugin-react": "^7.0.1", 36 | "pre-commit": "^1.2.2" 37 | }, 38 | "author": "", 39 | "license": "ISC" 40 | } 41 | -------------------------------------------------------------------------------- /containers/about.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { bindActionCreators } from 'redux'; 4 | import Layout from '!/components/layout'; 5 | import * as kittensActions from '!/actions/kittensActions'; 6 | /** 7 | * About component to show a list of kittens fetched from an external api. 8 | * Express api boilerplate with nextJS https://github.com/jsantana90/nextjs-express-boilerplate 9 | */ 10 | class About extends Component { 11 | static propTypes = { 12 | /** 13 | * an array/object response from api call, this is set from redux action. 14 | */ 15 | kittens: PropTypes.array.isRequired, 16 | /** 17 | * A redux function to set the kittens fetched from the api. 18 | */ 19 | setKittens: PropTypes.func.isRequired 20 | }; 21 | render() { 22 | const { kittens } = this.props; 23 | return ( 24 | 25 | {kittens 26 | ?
27 |

About us

28 |

We are kittens:

29 | 38 |
39 | :

Loading

} 40 |
41 | ); 42 | } 43 | } 44 | 45 | function mapStateToProps(state) { 46 | return { 47 | kittens: state.kittens 48 | }; 49 | } 50 | 51 | function mapDispatchToProps(dispatch) { 52 | return bindActionCreators(kittensActions, dispatch); 53 | } 54 | 55 | export default connect(mapStateToProps, mapDispatchToProps)(About); 56 | -------------------------------------------------------------------------------- /components/layout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Link from 'next/link'; 4 | import Router from 'next/router'; 5 | import Head from 'next/head'; 6 | 7 | class Layout extends Component { 8 | static propTypes = { 9 | title: PropTypes.string 10 | }; 11 | constructor() { 12 | super(); 13 | 14 | this.state = { aboutText: 'About' }; 15 | this.loading = this.loading.bind(this); 16 | } 17 | 18 | /** 19 | * when you click on the about link it will change the text in it and when it finish fetching 20 | * the /about route data from the api it will transition to the /about page 21 | */ 22 | loading() { 23 | if (Router.pathname !== '/about') { 24 | this.setState({ 25 | aboutText: ` 26 | Fetching about data before rendering page, this might take a while if now server 27 | was asleep... 28 | ` 29 | }); 30 | Router.push('/about'); 31 | } 32 | } 33 | render() { 34 | const { title, children } = this.props; 35 | const { aboutText } = this.state; 36 | return ( 37 |
38 | 39 | {title} 40 | 41 | 45 | 46 |
47 | 58 |
59 | 60 | {children} 61 | 62 | 65 |
66 | ); 67 | } 68 | } 69 | 70 | export default Layout; 71 | -------------------------------------------------------------------------------- /containers/home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | import { bindActionCreators } from 'redux'; 5 | import Layout from '!/components/layout'; 6 | import * as demoActions from '!/actions/demoActions'; 7 | /** 8 | * Home component to show basic redux usage with nextjs. 9 | */ 10 | class Home extends Component { 11 | static propTypes = { 12 | /** 13 | * demo string from redux actions. 14 | */ 15 | demoString: PropTypes.string.isRequired, 16 | /** 17 | * redux function from actions to set the string, accepts a string param, 18 | * if none is passed, it will return the default string set in the action. 19 | */ 20 | setString: PropTypes.func.isRequired 21 | }; 22 | 23 | constructor() { 24 | super(); 25 | this.changeDemoString = this.changeDemoString.bind(this); 26 | } 27 | 28 | componentDidMount() { 29 | const { setString } = this.props; 30 | console.log('properties: ', this.props); 31 | alert('setting default demo string to store'); 32 | setString(); 33 | } 34 | 35 | /** 36 | * Change the demo string to whatever you pass as a parameter. It will set the default string 37 | * if no parameter is passed. 38 | * @param {string} theString - String to be passed to show in component. 39 | */ 40 | changeDemoString(theString: String) { 41 | const { setString } = this.props; 42 | console.log('changing demo string to: ', theString); 43 | setString(theString); 44 | } 45 | 46 | render() { 47 | const { demoString } = this.props; 48 | return ( 49 | 50 | 53 | 56 |

{demoString}

57 |
58 | ); 59 | } 60 | } 61 | 62 | function mapStateToProps(state) { 63 | return { 64 | demoString: state.demoString 65 | }; 66 | } 67 | 68 | function mapDispatchToProps(dispatch) { 69 | return bindActionCreators(demoActions, dispatch); 70 | } 71 | 72 | export default connect(mapStateToProps, mapDispatchToProps)(Home); 73 | --------------------------------------------------------------------------------