├── .editorconfig ├── .eslintrc ├── .gitignore ├── README.md ├── nwb.config.js ├── package.json ├── public └── .gitkeep └── src ├── actions └── items.js ├── components └── ItemList.js ├── index.html ├── index.js ├── reducers ├── index.js └── items.js └── store └── configureStore.js /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist 3 | /node_modules 4 | npm-debug.log* 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'react-app' 3 | } 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stowball/dummys-guide-to-redux-and-thunk-react/1b4a36a2f7024ec61aea040c41cc5c89ad783d47/public/.gitkeep -------------------------------------------------------------------------------- /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 | 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------