├── .babelrc.js
├── .gitignore
├── README.md
├── client
├── __tests__
│ └── components
│ │ ├── Container.js
│ │ └── DumbComponent.js
└── src
│ ├── action
│ └── actions.js
│ ├── components
│ └── DumbComponent.jsx
│ ├── constants
│ └── actionTypes.js
│ ├── containers
│ ├── App.scss
│ └── Container.jsx
│ ├── index.js
│ ├── reducers
│ ├── combinedReducers.js
│ └── firstReducer.js
│ └── store.js
├── package.json
├── public
├── bundle.js
└── index.html
├── server
└── server.js
└── webpack.config.js
/.babelrc.js:
--------------------------------------------------------------------------------
1 | const isTest = String(process.env.NODE_DEV) === 'test'
2 |
3 | module.exports = {
4 | "presets": ["env", "react", "stage-2"],
5 | "env": {
6 | "test": {
7 | "presets": ["env"]
8 | },
9 | }
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # xavyr-react-redux-boilerplate
2 | Instant React/Redux Boilerplate
3 |
4 | Setting up React and Redux can take a few moments and be a pain. This repository is the simplest React example application that adheres to the container/component react paradigm.
5 |
6 | Jest and SASS are configured using Babel.
7 |
8 | With regards to Redux I've instantiated a single reducer, very simple store and two action dispatchers; one synchronous action and one asynchronous using Thunk middleware.
9 |
10 | I hope this gets you up to speed with regards to your react/redux applications faster.
11 |
--------------------------------------------------------------------------------
/client/__tests__/components/Container.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xavyr/react-redux-boilerplate/548aa2c192c3260df6117fc60f63f08673273c04/client/__tests__/components/Container.js
--------------------------------------------------------------------------------
/client/__tests__/components/DumbComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import DumbComponent from '../components/DumbComponent'
4 | import { syncAction } from '../action/actions';
5 |
6 | function sum(a, b) {
7 | return a + b;
8 | }
9 |
10 | test('adds 1 + 2 to equal 3', () => {
11 | expect(sum(1, 2)).toBe(3);
12 | });
13 |
14 | test('DumbComponent renders just fine', () => {
15 | const container = document.createElement('div')
16 | ReactDOM.render(, container)
17 | })
--------------------------------------------------------------------------------
/client/src/action/actions.js:
--------------------------------------------------------------------------------
1 | import * as types from '../constants/actionTypes.js'
2 |
3 | export const asyncAction = (currValue) => {
4 | return dispatch => {
5 | console.log(String(currValue) + ' => ' + String(!currValue) + ' in 3, 2, 1...');
6 | return setTimeout(function () {
7 | return dispatch({ type: types.ASYNC_ACTION, payload: !currValue })
8 | }, 3000);
9 | }
10 | };
11 |
12 | export const syncAction = (currValue) => {
13 | return { type: types.SYNC_ACTION, payload: !currValue }
14 | }
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/client/src/components/DumbComponent.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const DumbComponent = props =>
4 |
5 |
9 |
14 |
15 |
{JSON.stringify(props.syncData)}
16 | {JSON.stringify(props.asyncData)}
17 |
18 |
19 |
20 | export default DumbComponent;
--------------------------------------------------------------------------------
/client/src/constants/actionTypes.js:
--------------------------------------------------------------------------------
1 | export const SYNC_ACTION = "SYNC_ACTION";
2 | export const ASYNC_ACTION = "ASYNC_ACTION";
3 |
--------------------------------------------------------------------------------
/client/src/containers/App.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | font: 100% Helvetica, sans-serif;
3 | color: red;
4 | text-align: center;
5 | }
--------------------------------------------------------------------------------
/client/src/containers/Container.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import DumbComponent from '../components/DumbComponent.jsx';
3 | import { connect } from 'react-redux';
4 | import { bindActionCreators } from 'redux';
5 | import * as actions from '../action/actions'
6 | import './App.scss';
7 |
8 |
9 | const mapStateToProps = (store) => {
10 | return {
11 | syncData: store.firstReducer.syncData,
12 | asyncData: store.firstReducer.asyncData,
13 | }
14 | }
15 |
16 | const mapDispatchToProps = (dispatch) => {
17 | return bindActionCreators({
18 | syncAction: actions.syncAction,
19 | asyncAction: actions.asyncAction
20 | }, dispatch)
21 | };
22 |
23 |
24 | class Container extends Component {
25 |
26 | componentDidMount() {
27 | console.log('exampleContainer did mount');
28 | }
29 |
30 | render() {
31 | return (
32 |
33 |
Container
34 |
40 |
41 | );
42 | }
43 | }
44 |
45 | export default connect(mapStateToProps, mapDispatchToProps)(Container);
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import store from './store.js';
5 | import App from './../src/containers/Container.jsx'
6 | ReactDOM.render(
7 |
8 |
9 | , document.getElementById('root'));
10 |
11 | module.hot.accept();
--------------------------------------------------------------------------------
/client/src/reducers/combinedReducers.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import firstReducer from './firstReducer.js'
3 |
4 |
5 | // combine reducers
6 | const reducers = combineReducers({
7 | // we only have a single reducer, but if we had others we could combine them here,
8 | //by mapping them to a key.
9 | firstReducer: firstReducer
10 | });
11 |
12 |
13 | export default reducers;
--------------------------------------------------------------------------------
/client/src/reducers/firstReducer.js:
--------------------------------------------------------------------------------
1 | const initialState = {
2 | syncData: false,
3 | asyncData: false,
4 | count: 0
5 | }
6 |
7 | //The action passed into our reducer comes from the dispatch.
8 | const firstReducer = (state = initialState, action) => {
9 | switch (action.type) {
10 | case 'SYNC_ACTION':
11 | return {
12 | ...state,
13 | syncData: action.payload
14 | }
15 | case 'ASYNC_ACTION':
16 | return {
17 | ...state,
18 | asyncData: action.payload
19 | }
20 | default:
21 | return state;
22 | }
23 | };
24 |
25 |
26 |
27 |
28 | export default firstReducer;
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/client/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { composeWithDevTools } from 'redux-devtools-extension';
4 | import reducers from './reducers/combinedReducers.js';
5 |
6 |
7 | //the store, enhanced with thunk middleware to allow for async action in redux.
8 | const store = createStore(
9 | reducers,
10 | composeWithDevTools(
11 | applyMiddleware(thunk)
12 | )
13 | );
14 |
15 |
16 | export default store;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-boilerplate",
3 | "version": "1.0.0",
4 | "description": "Modern React Redux Boilerplate",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon ./server/server.js & webpack-dev-server --config ./webpack.config.js --mode development --hot",
8 | "test": "jest"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/Xavyr/react-redux-boilerplate"
13 | },
14 | "author": "Xavyr",
15 | "license": "MIT",
16 | "homepage": "https://github.com/Xavyr/react-redux-boilerplate",
17 | "dependencies": {
18 | "css-loader": "^1.0.0",
19 | "react": "^16.3.2",
20 | "react-dom": "^16.3.2",
21 | "react-hot-loader": "^4.2.0",
22 | "react-redux": "^5.0.7",
23 | "redux": "^4.0.0",
24 | "redux-devtools-extension": "^2.13.2",
25 | "redux-thunk": "^2.2.0",
26 | "style-loader": "^0.23.0"
27 | },
28 | "devDependencies": {
29 | "babel-core": "^6.26.3",
30 | "babel-jest": "^23.6.0",
31 | "babel-loader": "^7.1.4",
32 | "babel-preset-env": "^1.7.0",
33 | "babel-preset-react": "^6.24.1",
34 | "babel-preset-stage-2": "^6.24.1",
35 | "jest": "^23.6.0",
36 | "node-sass": "^4.9.3",
37 | "sass-loader": "^7.1.0",
38 | "webpack": "^4.17.2",
39 | "webpack-cli": "^2.1.3",
40 | "webpack-dev-server": "^3.1.4"
41 | },
42 | "babel": {
43 | "presets": "./.babelrc.js"
44 | },
45 | "jest": {}
46 | }
47 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React Boilerplate
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | //npm modules
2 | const express = require('express');
3 | const path = require('path');
4 |
5 | //the actual express app instance;
6 | const app = express();
7 |
8 | //parsing body for post routes
9 | var bodyParser = require('body-parser');
10 | app.use(bodyParser.json());
11 | app.use(bodyParser.urlencoded({ extended: true }));
12 |
13 | //pathing to specific files
14 | app.use(express.static(path.join(__dirname, './../public')));
15 |
16 | app.get('/api', (req, res) => {
17 | console.log('hit the home route');
18 | res.send('from home api route');
19 | });
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | app.listen(3000);
31 |
32 | module.exports = app;
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: './client/src/index.js',
5 | output: {
6 | path: path.join(__dirname, 'public'),
7 | filename: 'bundle.js'
8 | },
9 | module: {
10 | rules: [
11 | {
12 | loader: 'babel-loader',
13 | test: /\.js$|\.jsx$/,
14 | exclude: /node_modules/
15 | },
16 | {
17 | test: /\.scss$/,
18 | use: [{
19 | loader: "style-loader"
20 | }, {
21 | loader: "css-loader"
22 | }, {
23 | loader: "sass-loader",
24 | options: {
25 | includePaths: ["absolute/path/a", "absolute/path/b"]
26 | }
27 | }]
28 | },
29 | {
30 | test: /\.(png|svg|jpg|jpeg|gif)$/,
31 | use: [
32 | 'url-loader'
33 | ]
34 | }
35 | ]
36 | },
37 |
38 | // Dev tools are provided by webpack
39 | // Source maps help map errors to original react code
40 | devtool: 'cheap-module-eval-source-map',
41 |
42 | // Configuration for webpack-dev-server
43 | devServer: {
44 | contentBase: path.join(__dirname, 'public'),
45 | proxy: {
46 | "/api*": "http://localhost:3000"
47 | }
48 | },
49 | };
--------------------------------------------------------------------------------