├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── products.json
├── shopcart.json
├── src
├── actions
│ ├── index.js
│ ├── product.js
│ └── shopcart.js
├── app.js
├── components
│ ├── loading_view.js
│ ├── modal_view.js
│ ├── product_list.js
│ └── shop_cart.js
├── constants
│ └── actionTypes.js
├── containers
│ └── app.js
├── index.html
├── pages
│ ├── home.js
│ └── intro.js
└── reducers
│ ├── index.js
│ ├── product.js
│ └── shopcart.js
└── webpack.config.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": [
3 | "google",
4 | "plugin:react/recommended"
5 | ],
6 | "parser": "babel-eslint",
7 | "parserOptions": {
8 | "sourceType": "module",
9 | "allowImportExportEverywhere": true
10 | },
11 | "rules": {
12 | "max-len": [1, 120, 2, {
13 | "ignorePattern": "^import\\s.+\\sfrom\\s.+;$",
14 | "ignoreUrls": true
15 | }],
16 | }
17 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/node,webstorm,reactnative,visualstudiocode
3 |
4 | ### Node ###
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 |
24 | # nyc test coverage
25 | .nyc_output
26 |
27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
28 | .grunt
29 |
30 | # Bower dependency directory (https://bower.io/)
31 | bower_components
32 |
33 | # node-waf configuration
34 | .lock-wscript
35 |
36 | # Compiled binary addons (http://nodejs.org/api/addons.html)
37 | build/Release
38 |
39 | # Dependency directories
40 | node_modules/
41 | jspm_packages/
42 |
43 | # Typescript v1 declaration files
44 | typings/
45 |
46 | # Optional npm cache directory
47 | .npm
48 |
49 | # Optional eslint cache
50 | .eslintcache
51 |
52 | # Optional REPL history
53 | .node_repl_history
54 |
55 | # Output of 'npm pack'
56 | *.tgz
57 |
58 | # Yarn Integrity file
59 | .yarn-integrity
60 |
61 | # dotenv environment variables file
62 | .env
63 |
64 |
65 | ### ReactNative ###
66 | # React Native Stack Base
67 | ### ReactNative.macOS Stack ###
68 | *.DS_Store
69 | .AppleDouble
70 | .LSOverride
71 |
72 | # Icon must end with two \r
73 | Icon
74 |
75 |
76 | # Thumbnails
77 | ._*
78 |
79 | # Files that might appear in the root of a volume
80 | .DocumentRevisions-V100
81 | .fseventsd
82 | .Spotlight-V100
83 | .TemporaryItems
84 | .Trashes
85 | .VolumeIcon.icns
86 | .com.apple.timemachine.donotpresent
87 |
88 | # Directories potentially created on remote AFP share
89 | .AppleDB
90 | .AppleDesktop
91 | Network Trash Folder
92 | Temporary Items
93 | .apdisk
94 |
95 | ### ReactNative.Linux Stack ###
96 | *~
97 |
98 | # temporary files which can be created if a process still has a handle open of a deleted file
99 | .fuse_hidden*
100 |
101 | # KDE directory preferences
102 | .directory
103 |
104 | # Linux trash folder which might appear on any partition or disk
105 | .Trash-*
106 |
107 | # .nfs files are created when an open file is removed but is still being accessed
108 | .nfs*
109 |
110 | ### ReactNative.Android Stack ###
111 | # Built application files
112 | *.apk
113 | *.ap_
114 |
115 | # Files for the ART/Dalvik VM
116 | *.dex
117 |
118 | # Java class files
119 | *.class
120 |
121 | # Generated files
122 | bin/
123 | gen/
124 | out/
125 |
126 | # Gradle files
127 | .gradle/
128 | build/
129 |
130 | # Local configuration file (sdk path, etc)
131 | local.properties
132 |
133 | # Proguard folder generated by Eclipse
134 | proguard/
135 |
136 | # Log Files
137 |
138 | # Android Studio Navigation editor temp files
139 | .navigation/
140 |
141 | # Android Studio captures folder
142 | captures/
143 |
144 | # Intellij
145 | *.iml
146 | .idea/workspace.xml
147 | .idea/tasks.xml
148 | .idea/gradle.xml
149 | .idea/dictionaries
150 | .idea/libraries
151 |
152 | # Keystore files
153 | *.jks
154 |
155 | # External native build folder generated in Android Studio 2.2 and later
156 | .externalNativeBuild
157 |
158 | # Google Services (e.g. APIs or Firebase)
159 | google-services.json
160 |
161 | # Freeline
162 | freeline.py
163 | freeline/
164 | freeline_project_description.json
165 |
166 | ### ReactNative.Gradle Stack ###
167 | .gradle
168 | /build/
169 |
170 | # Ignore Gradle GUI config
171 | gradle-app.setting
172 |
173 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
174 | !gradle-wrapper.jar
175 |
176 | # Cache of project
177 | .gradletasknamecache
178 |
179 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
180 | # gradle/wrapper/gradle-wrapper.properties
181 |
182 | ### ReactNative.Node Stack ###
183 | # Logs
184 |
185 | # Runtime data
186 |
187 | # Directory for instrumented libs generated by jscoverage/JSCover
188 |
189 | # Coverage directory used by tools like istanbul
190 |
191 | # nyc test coverage
192 |
193 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
194 |
195 | # Bower dependency directory (https://bower.io/)
196 |
197 | # node-waf configuration
198 |
199 | # Compiled binary addons (http://nodejs.org/api/addons.html)
200 |
201 | # Dependency directories
202 |
203 | # Typescript v1 declaration files
204 |
205 | # Optional npm cache directory
206 |
207 | # Optional eslint cache
208 |
209 | # Optional REPL history
210 |
211 | # Output of 'npm pack'
212 |
213 | # Yarn Integrity file
214 |
215 | # dotenv environment variables file
216 |
217 |
218 | ### ReactNative.Xcode Stack ###
219 | # Xcode
220 | #
221 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
222 |
223 | ## Build generated
224 | DerivedData/
225 |
226 | ## Various settings
227 | *.pbxuser
228 | !default.pbxuser
229 | *.mode1v3
230 | !default.mode1v3
231 | *.mode2v3
232 | !default.mode2v3
233 | *.perspectivev3
234 | !default.perspectivev3
235 | xcuserdata/
236 |
237 | ## Other
238 | *.moved-aside
239 | *.xccheckout
240 | *.xcscmblueprint
241 |
242 | ### ReactNative.Buck Stack ###
243 | buck-out/
244 | .buckconfig.local
245 | .buckd/
246 | .buckversion
247 | .fakebuckversion
248 |
249 | ### VisualStudioCode ###
250 | .vscode/*
251 | !.vscode/settings.json
252 | !.vscode/tasks.json
253 | !.vscode/launch.json
254 | !.vscode/extensions.json
255 |
256 | ### WebStorm ###
257 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
258 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
259 |
260 | # User-specific stuff:
261 | .idea/**/workspace.xml
262 | .idea/**/tasks.xml
263 |
264 | # Sensitive or high-churn files:
265 | .idea/**/dataSources/
266 | .idea/**/dataSources.ids
267 | .idea/**/dataSources.xml
268 | .idea/**/dataSources.local.xml
269 | .idea/**/sqlDataSources.xml
270 | .idea/**/dynamic.xml
271 | .idea/**/uiDesigner.xml
272 |
273 | # Gradle:
274 | .idea/**/gradle.xml
275 | .idea/**/libraries
276 |
277 | # CMake
278 | cmake-build-debug/
279 |
280 | # Mongo Explorer plugin:
281 | .idea/**/mongoSettings.xml
282 |
283 | ## File-based project format:
284 | *.iws
285 |
286 | ## Plugin-specific files:
287 |
288 | # IntelliJ
289 | /out/
290 |
291 | # mpeltonen/sbt-idea plugin
292 | .idea_modules/
293 |
294 | # JIRA plugin
295 | atlassian-ide-plugin.xml
296 |
297 | # Cursive Clojure plugin
298 | .idea/replstate.xml
299 |
300 | # Crashlytics plugin (for Android Studio and IntelliJ)
301 | com_crashlytics_export_strings.xml
302 | crashlytics.properties
303 | crashlytics-build.properties
304 | fabric.properties
305 |
306 | ### WebStorm Patch ###
307 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
308 |
309 | # *.iml
310 | # modules.xml
311 | # .idea/misc.xml
312 | # *.ipr
313 |
314 | # Sonarlint plugin
315 | .idea/sonarlint
316 | # End of https://www.gitignore.io/api/node,webstorm,reactnative,visualstudiocode
317 |
318 | dist/
319 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Eric Ping
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## React Redux Shopcart Example
2 | This is an example for beginners to learn redux.
3 | ## Prerequisites
4 | Latest Node.js and NPM
5 | ### Installation
6 | ```bash
7 | npm install
8 | ```
9 | ### Usage
10 | ```bash
11 | npm start
12 | ```
13 | and open http://localhost:8080 in the browser
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-shopcart",
3 | "version": "1.0.0",
4 | "description": "React + Redux + Shopcart Example",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server",
8 | "build": "NODE_ENV=production webpack",
9 | "test": "echo \"Error: no test specified\" && exit 1",
10 | "eslint": "./node_modules/.bin/eslint ./src/*.js* ./src/**/*.js* --fix"
11 | },
12 | "keywords": [
13 | "react",
14 | "redux",
15 | "shopcart"
16 | ],
17 | "author": "Eric Ping",
18 | "license": "MIT",
19 | "devDependencies": {
20 | "babel-cli": "^6.24.1",
21 | "babel-core": "^6.25.0",
22 | "babel-eslint": "^7.2.3",
23 | "babel-loader": "^7.0.0",
24 | "babel-preset-env": "^1.5.2",
25 | "babel-preset-react": "^6.24.1",
26 | "babel-preset-stage-3": "^6.24.1",
27 | "css-loader": "^0.28.4",
28 | "eslint": "^4.0.0",
29 | "eslint-config-google": "^0.8.0",
30 | "eslint-plugin-react": "^7.0.1",
31 | "file-loader": "^0.11.2",
32 | "html-loader": "^0.5.1",
33 | "html-webpack-plugin": "^2.30.1",
34 | "node-sass": "^4.5.3",
35 | "sass-loader": "^6.0.5",
36 | "style-loader": "^0.18.2",
37 | "webpack": "^2.6.1",
38 | "webpack-dev-server": "^2.4.5"
39 | },
40 | "dependencies": {
41 | "bootstrap": "^4.0.0",
42 | "classnames": "^2.2.5",
43 | "colors": "^1.1.2",
44 | "font-awesome": "^4.7.0",
45 | "history": "^4.7.2",
46 | "jquery": "^3.2.1",
47 | "popper.js": "^1.12.9",
48 | "prop-types": "^15.5.10",
49 | "react": "^16.0.0",
50 | "react-dom": "^16.0.0",
51 | "react-redux": "^5.0.5",
52 | "react-router": "^4.2.0",
53 | "react-router-dom": "^4.2.2",
54 | "react-router-redux": "^5.0.0-alpha.8",
55 | "redux": "^3.7.0",
56 | "redux-thunk": "^2.2.0",
57 | "styled-jsx": "^2.1.2"
58 | },
59 | "repository": {
60 | "type": "git",
61 | "url": "https://github.com/EricPing/react-redux-shopcart.git"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/products.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 1,
4 | "title": "哈利波特 - 神秘的魔法石",
5 | "img": "https://unsplash.it/400/300/?random&1",
6 | "price": 15,
7 | "discount": 5
8 | },
9 | {
10 | "id": 2,
11 | "title": "波西傑克森 - 神火之賊",
12 | "img": "https://unsplash.it/400/300/?random&2",
13 | "price": 20,
14 | "discount": 5
15 | },
16 | {
17 | "id": 3,
18 | "title": "刀劍神域 1",
19 | "img": "https://unsplash.it/400/300/?random&3",
20 | "price": 25,
21 | "discount": 5
22 | },
23 | {
24 | "id": 4,
25 | "title": "加速世界 1",
26 | "img": "https://unsplash.it/400/300/?random&1",
27 | "price": 15,
28 | "discount": 5
29 | },
30 | {
31 | "id": 5,
32 | "title": "零之使魔 1",
33 | "img": "https://unsplash.it/400/300/?random&2",
34 | "price": 20,
35 | "discount": 5
36 | },
37 | {
38 | "id": 6,
39 | "title": "歷史守護者 1",
40 | "img": "https://unsplash.it/400/300/?random&3",
41 | "price": 25,
42 | "discount": 5
43 | }
44 | ]
--------------------------------------------------------------------------------
/shopcart.json:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/src/actions/index.js:
--------------------------------------------------------------------------------
1 | import * as productActions from './product';
2 | import * as shopcartActions from './shopcart';
3 |
4 | export default {...productActions, ...shopcartActions};
5 |
--------------------------------------------------------------------------------
/src/actions/product.js:
--------------------------------------------------------------------------------
1 | import {GETTING_PRODUCT_LIST,
2 | GET_PRODUCT_LIST_SUCCESS} from '../constants/actionTypes';
3 |
4 | /**
5 | * 這邊使用setTimeout是因為localhost載入資料很快,為了做到Loading的效果,所以這邊延長時間
6 | * @return {function} async function
7 | */
8 | export function getProducts() {
9 | return (dispatch) => {
10 | dispatch({type: GETTING_PRODUCT_LIST});
11 | setTimeout(() => {
12 | fetch('/products.json')
13 | .then((response) => response.json())
14 | .then((productList) => {
15 | dispatch({type: GET_PRODUCT_LIST_SUCCESS, product_list: productList});
16 | });
17 | }, 3000);
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/src/actions/shopcart.js:
--------------------------------------------------------------------------------
1 | import {GETTING_SHOPCART_LIST,
2 | GET_SHOPCART_LIST_SUCCESS,
3 | ADDING_TO_SHOPCART,
4 | ADDED_TO_SHOPCART,
5 | DELETE_FROM_SHOPCART,
6 | UPDATE_AMOUNT_TO_SHOPCART,
7 | ENABLE_ADD_TO_SHOPCART,
8 | DISABLE_ADD_TO_SHOPCART} from '../constants/actionTypes';
9 |
10 | /**
11 | * 這邊使用setTimeout是因為localhost載入資料很快,為了做到Loading的效果,所以這邊延長時間
12 | * @return {function} async function
13 | */
14 | export function getShopList() {
15 | return (dispatch) => {
16 | dispatch({type: GETTING_SHOPCART_LIST});
17 | setTimeout(() => {
18 | fetch('/shopcart.json')
19 | .then((response) => response.json())
20 | .then((shopcartList) => {
21 | dispatch({type: GET_SHOPCART_LIST_SUCCESS, shopcart_list: shopcartList});
22 | });
23 | }, 3000);
24 | };
25 | };
26 |
27 | /**
28 | * @param {int} id
29 | * @param {object} product
30 | * @param {int} amount
31 | * @return {function} async function
32 | */
33 | export function addToShopcart(id, product, amount = 1) {
34 | return (dispatch) => {
35 | dispatch({type: ADDING_TO_SHOPCART});
36 | dispatch({type: DISABLE_ADD_TO_SHOPCART, id: id});
37 | setTimeout(() => {
38 | dispatch({type: ADDED_TO_SHOPCART, product: product, amount: amount});
39 | }, 1500);
40 | };
41 | }
42 |
43 | /**
44 | *
45 | * @param {int} index
46 | * @param {int} amount
47 | * @return {function} function
48 | */
49 | export function updateAmountToShopcart(index, amount) {
50 | return (dispatch) => {
51 | dispatch({type: UPDATE_AMOUNT_TO_SHOPCART, index: index, amount: amount});
52 | };
53 | }
54 |
55 | /**
56 | * @param {int} id
57 | * @param {int} index
58 | * @return {function} function
59 | */
60 | export function deleteFromShopcart(id, index) {
61 | return (dispatch) => {
62 | dispatch({type: ENABLE_ADD_TO_SHOPCART, id: id});
63 | dispatch({type: DELETE_FROM_SHOPCART, index: index});
64 | };
65 | }
66 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | import 'bootstrap/dist/css/bootstrap.css';
2 | // import 'font-awesome/css/font-awesome.css';
3 | import 'bootstrap/dist/js/bootstrap';
4 | import React from 'react';
5 | import ReactDOM from 'react-dom';
6 | import AppContainer from './containers/app';
7 | import {createStore, applyMiddleware, combineReducers} from 'redux';
8 | import {Provider} from 'react-redux';
9 | import createHistory from 'history/createBrowserHistory';
10 | import {ConnectedRouter, routerReducer, routerMiddleware} from 'react-router-redux';
11 | import thunk from 'redux-thunk';
12 | import reducers from './reducers';
13 |
14 | const history = createHistory();
15 |
16 | const store = createStore(
17 | combineReducers({routerReducer, ...reducers}),
18 | applyMiddleware(routerMiddleware(history), thunk),
19 | );
20 |
21 | ReactDOM.render(
22 |
{content}
42 |{product.price - product.discount}
89 |{total}
# | 116 |品項 | 117 |數量 | 118 |價錢 | 119 |小計 | 120 |動作 | 121 |
---|---|---|---|---|---|
126 | | 127 | | 128 | | 總計 | 129 |{this.props.store.sum} | 130 |131 | 134 | | 135 |
這只是個練習用購物車
24 |購物車金額: {this.props.shopcartStore.sum}
25 | 返回購物車 26 |