├── .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 |
30 | {kittens.map((cat, index) => {
31 | return (
32 | -
33 | {cat.name}
34 |
35 | );
36 | })}
37 |
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 |
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 |
--------------------------------------------------------------------------------