├── public
└── .gitkeep
├── nwb.config.js
├── .gitignore
├── src
├── reducers
│ ├── index.js
│ └── items.js
├── store
│ └── configureStore.js
├── index.js
├── index.html
├── actions
│ └── items.js
└── components
│ └── ItemList.js
├── .editorconfig
├── package.json
├── README.md
└── .eslintrc
/public/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nwb.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | type: 'react-app'
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /dist
3 | /node_modules
4 | npm-debug.log*
5 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { items, itemsHasErrored, itemsIsLoading } from './items';
3 |
4 | export default combineReducers({
5 | items,
6 | itemsHasErrored,
7 | itemsIsLoading
8 | });
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*.{css,html,js,scss}]
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/src/store/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import rootReducer from '../reducers';
4 |
5 | export default function configureStore(initialState) {
6 | return createStore(
7 | rootReducer,
8 | initialState,
9 | applyMiddleware(thunk)
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import configureStore from './store/configureStore';
5 |
6 | import ItemList from './components/ItemList';
7 |
8 | const store = configureStore();
9 |
10 | render(
11 |
12 |
13 | ,
14 | document.getElementById('app')
15 | );
16 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | A Dummy's Guide to Redux and Thunk in React
7 |
8 |
9 |
10 |
11 | A Dummy's Guide to Redux and Thunk in React
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dummys-guide-to-redux-and-thunk-react",
3 | "version": "1.0.0",
4 | "description": "A Dummy's Guide to Redux and Thunk in React",
5 | "private": true,
6 | "scripts": {
7 | "build": "nwb build-react-app",
8 | "clean": "nwb clean-app",
9 | "start": "nwb serve-react-app"
10 | },
11 | "dependencies": {
12 | "react": "^15.3.2",
13 | "react-dom": "^15.3.2",
14 | "react-redux": "^4.4.5",
15 | "redux": "^3.6.0",
16 | "redux-thunk": "^2.1.0"
17 | },
18 | "devDependencies": {
19 | "babel-eslint": "^7.1.0",
20 | "eslint": "^3.10.0",
21 | "eslint-plugin-react": "^6.6.0",
22 | "nwb": "0.12.x"
23 | },
24 | "author": "",
25 | "license": "MIT",
26 | "repository": ""
27 | }
28 |
--------------------------------------------------------------------------------
/src/reducers/items.js:
--------------------------------------------------------------------------------
1 | export function itemsHasErrored(state = false, action) {
2 | switch (action.type) {
3 | case 'ITEMS_HAS_ERRORED':
4 | return action.hasErrored;
5 |
6 | default:
7 | return state;
8 | }
9 | }
10 |
11 | export function itemsIsLoading(state = false, action) {
12 | switch (action.type) {
13 | case 'ITEMS_IS_LOADING':
14 | return action.isLoading;
15 |
16 | default:
17 | return state;
18 | }
19 | }
20 |
21 | export function items(state = [], action) {
22 | switch (action.type) {
23 | case 'ITEMS_FETCH_DATA_SUCCESS':
24 | return action.items;
25 |
26 | default:
27 | return state;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A Dummy's Guide to Redux and Thunk in React
2 |
3 | The demo app for my blog post. It is not an example of writing best-practice markup or React JavaScript, it's just the bare minimum to demonstrate how to use Redux and Redux Thunk.
4 |
5 | ## Prerequisites
6 |
7 | [Node.js](http://nodejs.org/) >= v4 must be installed.
8 |
9 | ## Installation
10 |
11 | - Running `npm install` in the app's root directory will install everything you need for development.
12 |
13 | ## Development Server
14 |
15 | - `npm start` will run the app's development server at [http://localhost:3000](http://localhost:3000) with hot module reloading.
16 |
17 | ## Building
18 |
19 | - `npm run build` creates a production build by default.
20 |
21 | To create a development build, set the `NODE_ENV` environment variable to `development` while running this command.
22 |
23 | - `npm run clean` will delete built resources.
24 |
--------------------------------------------------------------------------------
/src/actions/items.js:
--------------------------------------------------------------------------------
1 | export function itemsHasErrored(bool) {
2 | return {
3 | type: 'ITEMS_HAS_ERRORED',
4 | hasErrored: bool
5 | };
6 | }
7 |
8 | export function itemsIsLoading(bool) {
9 | return {
10 | type: 'ITEMS_IS_LOADING',
11 | isLoading: bool
12 | };
13 | }
14 |
15 | export function itemsFetchDataSuccess(items) {
16 | return {
17 | type: 'ITEMS_FETCH_DATA_SUCCESS',
18 | items
19 | };
20 | }
21 |
22 | export function itemsFetchData(url) {
23 | return (dispatch) => {
24 | dispatch(itemsIsLoading(true));
25 |
26 | fetch(url)
27 | .then((response) => {
28 | if (!response.ok) {
29 | throw Error(response.statusText);
30 | }
31 |
32 | dispatch(itemsIsLoading(false));
33 |
34 | return response;
35 | })
36 | .then((response) => response.json())
37 | .then((items) => dispatch(itemsFetchDataSuccess(items)))
38 | .catch(() => dispatch(itemsHasErrored(true)));
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/ItemList.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { connect } from 'react-redux';
3 | import { itemsFetchData } from '../actions/items';
4 |
5 | class ItemList extends Component {
6 | componentDidMount() {
7 | this.props.fetchData('http://599167402df2f40011e4929a.mockapi.io/items');
8 | }
9 |
10 | render() {
11 | if (this.props.hasErrored) {
12 | return Sorry! There was an error loading the items
;
13 | }
14 |
15 | if (this.props.isLoading) {
16 | return Loading…
;
17 | }
18 |
19 | return (
20 |
21 | {this.props.items.map((item) => (
22 | -
23 | {item.label}
24 |
25 | ))}
26 |
27 | );
28 | }
29 | }
30 |
31 | ItemList.propTypes = {
32 | fetchData: PropTypes.func.isRequired,
33 | items: PropTypes.array.isRequired,
34 | hasErrored: PropTypes.bool.isRequired,
35 | isLoading: PropTypes.bool.isRequired
36 | };
37 |
38 | const mapStateToProps = (state) => {
39 | return {
40 | items: state.items,
41 | hasErrored: state.itemsHasErrored,
42 | isLoading: state.itemsIsLoading
43 | };
44 | };
45 |
46 | const mapDispatchToProps = (dispatch) => {
47 | return {
48 | fetchData: (url) => dispatch(itemsFetchData(url))
49 | };
50 | };
51 |
52 | export default connect(mapStateToProps, mapDispatchToProps)(ItemList);
53 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "parserOptions": {
4 | "ecmaVersion": 6,
5 | "ecmaFeatures": {
6 | "jsx": true
7 | },
8 | "sourceType": "module"
9 | },
10 | "env": {
11 | "browser": true,
12 | "es6": true
13 | },
14 | "globals": {
15 | "module": true,
16 | },
17 | "plugins": [
18 | "react"
19 | ],
20 | "extends": [
21 | "plugin:react/recommended"
22 | ],
23 | "rules": {
24 | "comma-dangle": [2, "never"],
25 | "no-cond-assign": [2, "except-parens"],
26 | "no-console": 1,
27 | "no-constant-condition": 2,
28 | "no-control-regex": 2,
29 | "no-debugger": 2,
30 | "no-dupe-args": 2,
31 | "no-dupe-keys": 2,
32 | "no-duplicate-case": 2,
33 | "no-empty": 1,
34 | "no-empty-character-class": 2,
35 | "no-ex-assign": 2,
36 | "no-extra-boolean-cast": 2,
37 | "no-extra-parens": [2, "functions"],
38 | "no-extra-semi": 2,
39 | "no-func-assign": 2,
40 | "no-invalid-regexp": 2,
41 | "no-irregular-whitespace": 2,
42 | "no-obj-calls": 2,
43 | "no-regex-spaces": 1,
44 | "no-sparse-arrays": 2,
45 | "no-unexpected-multiline": 2,
46 | "no-unreachable": 2,
47 | "no-unsafe-negation": 2,
48 | "use-isnan": 2,
49 | "valid-typeof": 2,
50 |
51 | "accessor-pairs": [2, { "getWithoutSet":true }],
52 | "block-scoped-var": 2,
53 | "curly": [2, "all"],
54 | "dot-location": [2, "property"],
55 | "eqeqeq": [2, "smart"],
56 | "no-alert": 2,
57 | "no-else-return": 2,
58 | "no-eval": 2,
59 | "no-extra-bind": 2,
60 | "no-fallthrough": 2,
61 | "no-floating-decimal": 2,
62 | "no-global-assign": 2,
63 | "no-implicit-globals": 2,
64 | "no-implied-eval": 2,
65 | "no-labels": 2,
66 | "no-multi-spaces": 2,
67 | "no-multi-str": 2,
68 | "no-new-func": 2,
69 | "no-octal-escape": 2,
70 | "no-octal": 2,
71 | "no-redeclare": 2,
72 | "no-return-assign": [2, "except-parens"],
73 | "no-script-url": 2,
74 | "no-self-assign": 2,
75 | "no-throw-literal": 2,
76 | "no-unused-labels": 2,
77 | "no-useless-call": 2,
78 | "no-useless-concat": 2,
79 | "no-void": 2,
80 | "no-with": 2,
81 | "radix": 2,
82 | "wrap-iife": [2, "inside"],
83 | "yoda": [2, "never", {}],
84 |
85 | "strict": [2,"function"],
86 |
87 | "no-delete-var": 2,
88 | "no-shadow": [2, { "builtinGlobals": false, "hoist": "functions", "allow": [] }],
89 | "no-shadow-restricted-names": 2,
90 | "no-undef": 2,
91 | "no-undef-init": 2,
92 | "no-unused-vars": 2,
93 | "no-use-before-define": [2, "nofunc"],
94 |
95 | "array-bracket-spacing": [2, "never"],
96 | "block-spacing": [2, "always"],
97 | "brace-style": [2, "stroustrup", { "allowSingleLine": false }],
98 | "comma-spacing": [2, { "before": false, "after": true }],
99 | "comma-style": [2, "last"],
100 | "computed-property-spacing": [2, "never"],
101 | "eol-last": 2,
102 | "func-call-spacing": [2, "never"],
103 | "id-match": [2, "^([A-Z])|([A-Z][a-z])|([a-z]+(([A-Z][a-z]+)|(__([A-Z][a-z]+)))*)$"],
104 | "indent": [2, 4, { "SwitchCase": 1 }],
105 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
106 | "keyword-spacing": 2,
107 | "linebreak-style": [2, "unix"],
108 | "new-cap": 2,
109 | "new-parens": 2,
110 | "newline-after-var": [2, "always"],
111 | "newline-before-return": 2,
112 | "no-lonely-if": 2,
113 | "no-mixed-spaces-and-tabs": [2, "smart-tabs"],
114 | "no-multiple-empty-lines": [2, { "max": 2 }],
115 | "no-nested-ternary": 2,
116 | "no-tabs": 1,
117 | "no-trailing-spaces": [2, { "skipBlankLines": false }],
118 | "no-whitespace-before-property": 2,
119 | "quote-props": [2, "as-needed", { "unnecessary": false }],
120 | "quotes": [1, "single"],
121 | "semi": [2, "always"],
122 | "semi-spacing": [2, { "before": false, "after": true }],
123 | "space-before-blocks": [2, "always"],
124 | "space-before-function-paren": [2, "never"],
125 | "space-infix-ops": 2,
126 | "wrap-regex": 2,
127 |
128 | "react/jsx-uses-vars": 2
129 | }
130 | }
131 |
--------------------------------------------------------------------------------