├── 01_redux-intro-and-reducer-pattern
├── index.html
├── package-lock.json
├── package.json
└── script.js
├── 02_redux-devtools
├── index.html
├── package-lock.json
├── package.json
└── script.js
├── 03_make-your-own-redux
├── index.html
├── my-redux.js
├── package-lock.json
├── package.json
└── script.js
├── 04_manage-complex-state-using-redux
├── index.html
├── package-lock.json
├── package.json
├── productsList.js
└── script.js
├── 05_creating-multiple-reducers
├── cartReducer.js
├── index.html
├── package-lock.json
├── package.json
├── productsList.js
├── productsReducer.js
├── script.js
└── wishListReducer.js
├── 06_make-your-own-combineReducer-function
├── cartReducer.js
├── index.html
├── package-lock.json
├── package.json
├── productsList.js
├── productsReducer.js
├── script.js
└── wishListReducer.js
├── 07_action-creators-in-redux
├── cartReducer.js
├── index.html
├── package-lock.json
├── package.json
├── productsList.js
├── productsReducer.js
├── script.js
└── wishListReducer.js
├── 08_connect-redux-with-react
├── App.css
├── App.js
├── components
│ └── Product.js
├── index.html
├── main.js
├── package-lock.json
├── package.json
├── required-files
│ ├── App.css
│ └── Product.js
└── store
│ ├── cartReducer.js
│ ├── index.js
│ ├── productsList.js
│ ├── productsReducer.js
│ └── wishListReducer.js
├── 09_make-shopping-cart
├── final-code
│ ├── App.css
│ ├── App.js
│ ├── assets
│ │ └── cart-icon.svg
│ ├── components
│ │ ├── CartItem.js
│ │ ├── Header.js
│ │ └── Product.js
│ ├── index.html
│ ├── main.js
│ ├── package-lock.json
│ ├── package.json
│ ├── pages
│ │ ├── Cart.js
│ │ └── Home.js
│ └── store
│ │ ├── cartReducer.js
│ │ ├── index.js
│ │ ├── productsList.js
│ │ ├── productsReducer.js
│ │ └── wishListReducer.js
└── starter-code
│ ├── App.css
│ ├── App.js
│ ├── assets
│ └── cart-icon.svg
│ ├── components
│ ├── CartItem.js
│ ├── Header.js
│ └── Product.js
│ ├── index.html
│ ├── main.js
│ ├── package-lock.json
│ ├── package.json
│ ├── pages
│ ├── Cart.js
│ └── Home.js
│ └── store
│ ├── cartReducer.js
│ ├── index.js
│ ├── productsList.js
│ ├── productsReducer.js
│ └── wishListReducer.js
├── 10_make-your-own-react-redux
├── App.css
├── App.js
├── assets
│ └── cart-icon.svg
├── components
│ ├── CartItem.js
│ ├── Header.js
│ └── Product.js
├── index.html
├── main.js
├── package-lock.json
├── package.json
├── pages
│ ├── Cart.js
│ └── Home.js
├── react-redux.js
└── store
│ ├── cartReducer.js
│ ├── index.js
│ ├── productsList.js
│ ├── productsReducer.js
│ └── wishListReducer.js
├── 11_what-are-slices-in-redux
├── App.css
├── App.js
├── assets
│ └── cart-icon.svg
├── components
│ ├── CartItem.js
│ ├── Header.js
│ └── Product.js
├── index.html
├── main.js
├── package-lock.json
├── package.json
├── pages
│ ├── Cart.js
│ └── Home.js
├── react-redux.js
└── store
│ ├── index.js
│ ├── productsList.js
│ └── slices
│ ├── cartSlice.js
│ ├── productsSlice.js
│ └── wishListSlice.js
├── 12_immerJS-in-redux
├── App.css
├── App.js
├── assets
│ └── cart-icon.svg
├── components
│ ├── CartItem.js
│ ├── Header.js
│ └── Product.js
├── index.html
├── main.js
├── package-lock.json
├── package.json
├── pages
│ ├── Cart.js
│ └── Home.js
├── react-redux.js
└── store
│ ├── index.js
│ ├── productsList.js
│ └── slices
│ ├── cartSlice.js
│ ├── productsSlice.js
│ └── wishListSlice.js
├── 13_redux-toolkit
├── App.css
├── App.js
├── assets
│ └── cart-icon.svg
├── components
│ ├── CartItem.js
│ ├── Header.js
│ └── Product.js
├── index.html
├── main.js
├── package-lock.json
├── package.json
├── pages
│ ├── Cart.js
│ └── Home.js
├── react-redux.js
└── store
│ ├── index.js
│ ├── productsList.js
│ └── slices
│ ├── cartSlice.js
│ ├── productsSlice.js
│ └── wishListSlice.js
├── 14_make-your-own-redux-toolkit
├── App.css
├── App.js
├── assets
│ └── cart-icon.svg
├── components
│ ├── CartItem.js
│ ├── Header.js
│ └── Product.js
├── index.html
├── main.js
├── package-lock.json
├── package.json
├── pages
│ ├── Cart.js
│ └── Home.js
├── react-redux.js
├── redux-toolkit.js
└── store
│ ├── index.js
│ ├── productsList.js
│ └── slices
│ ├── cartSlice.js
│ ├── productsSlice.js
│ └── wishListSlice.js
├── 15_middlewares-in-redux
├── App.css
├── App.js
├── assets
│ └── cart-icon.svg
├── components
│ ├── CartItem.js
│ ├── Header.js
│ └── Product.js
├── index.html
├── main.js
├── package-lock.json
├── package.json
├── pages
│ ├── Cart.js
│ └── Home.js
├── react-redux.js
├── redux-toolkit.js
└── store
│ ├── index.js
│ ├── middleware
│ └── logger.js
│ ├── productsList.js
│ └── slices
│ ├── cartSlice.js
│ ├── productsSlice.js
│ └── wishListSlice.js
├── 16_making-api-calls-in-redux
├── App.css
├── App.js
├── assets
│ └── cart-icon.svg
├── components
│ ├── CartItem.js
│ ├── Header.js
│ └── Product.js
├── index.html
├── main.js
├── package-lock.json
├── package.json
├── pages
│ ├── Cart.js
│ └── Home.js
├── react-redux.js
├── redux-toolkit.js
└── store
│ ├── index.js
│ ├── middleware
│ └── logger.js
│ ├── productsList.js
│ └── slices
│ ├── cartSlice.js
│ ├── productsSlice.js
│ └── wishListSlice.js
├── 17_selectors-in-redux
├── App.css
├── App.js
├── assets
│ └── cart-icon.svg
├── components
│ ├── CartItem.js
│ ├── Header.js
│ └── Product.js
├── index.html
├── main.js
├── package-lock.json
├── package.json
├── pages
│ ├── Cart.js
│ └── Home.js
├── react-redux.js
├── redux-toolkit.js
└── store
│ ├── index.js
│ ├── middleware
│ └── logger.js
│ ├── productsList.js
│ └── slices
│ ├── cartSlice.js
│ ├── productsSlice.js
│ └── wishListSlice.js
├── 18_custom-api-middleware
├── App.css
├── App.js
├── assets
│ └── cart-icon.svg
├── components
│ ├── CartItem.js
│ ├── Header.js
│ └── Product.js
├── index.html
├── main.js
├── package-lock.json
├── package.json
├── pages
│ ├── Cart.js
│ └── Home.js
├── react-redux.js
├── redux-toolkit.js
└── store
│ ├── index.js
│ ├── middleware
│ ├── api.js
│ └── logger.js
│ ├── productsList.js
│ └── slices
│ ├── cartSlice.js
│ ├── productsSlice.js
│ └── wishListSlice.js
├── 19_redux-thunk
├── App.css
├── App.js
├── assets
│ └── cart-icon.svg
├── components
│ ├── CartItem.js
│ ├── Header.js
│ └── Product.js
├── index.html
├── main.js
├── package-lock.json
├── package.json
├── pages
│ ├── Cart.js
│ └── Home.js
├── react-redux.js
├── redux-toolkit.js
└── store
│ ├── index.js
│ ├── middleware
│ ├── api.js
│ ├── func.js
│ └── logger.js
│ ├── productsList.js
│ └── slices
│ ├── cartSlice.js
│ ├── productsSlice.js
│ └── wishListSlice.js
├── 20_createAsyncThunk
├── App.css
├── App.js
├── assets
│ └── cart-icon.svg
├── components
│ ├── CartItem.js
│ ├── Header.js
│ └── Product.js
├── index.html
├── main.js
├── package-lock.json
├── package.json
├── pages
│ ├── Cart.js
│ └── Home.js
├── react-redux.js
├── redux-toolkit.js
└── store
│ ├── index.js
│ ├── middleware
│ ├── api.js
│ ├── func.js
│ └── logger.js
│ ├── productsList.js
│ └── slices
│ ├── cartSlice.js
│ ├── productsSlice.js
│ └── wishListSlice.js
└── 21_rtk-query
├── final-code
├── README.md
├── db.json
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│ └── vite.svg
├── src
│ ├── App.jsx
│ ├── Home.jsx
│ ├── TaskItem.jsx
│ ├── apiSlice.js
│ ├── assets
│ │ └── react.svg
│ ├── index.css
│ ├── main.jsx
│ └── store.js
├── tailwind.config.js
└── vite.config.js
└── starter-code
├── README.md
├── db.json
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
└── vite.svg
├── src
├── App.jsx
├── Home.jsx
├── TaskItem.jsx
├── assets
│ └── react.svg
├── index.css
└── main.jsx
├── tailwind.config.js
└── vite.config.js
/01_redux-intro-and-reducer-pattern/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
12 | Redux Intro & Reducer Pattern
13 |
14 | Post:
15 |
16 |
17 |
--------------------------------------------------------------------------------
/01_redux-intro-and-reducer-pattern/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "00_redux-intro-and-reducer-pattern",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "redux": "^4.2.1"
13 | }
14 | },
15 | "node_modules/@babel/runtime": {
16 | "version": "7.23.1",
17 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz",
18 | "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==",
19 | "dependencies": {
20 | "regenerator-runtime": "^0.14.0"
21 | },
22 | "engines": {
23 | "node": ">=6.9.0"
24 | }
25 | },
26 | "node_modules/redux": {
27 | "version": "4.2.1",
28 | "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
29 | "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
30 | "dependencies": {
31 | "@babel/runtime": "^7.9.2"
32 | }
33 | },
34 | "node_modules/regenerator-runtime": {
35 | "version": "0.14.0",
36 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
37 | "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/01_redux-intro-and-reducer-pattern/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "redux": "^4.2.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/01_redux-intro-and-reducer-pattern/script.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux'
2 |
3 | const initialState = {
4 | post: 0,
5 | name: 'Anurag Singh',
6 | age: 26,
7 | }
8 |
9 | const INCREMENT = 'post/increment'
10 | const DECREMENT = 'post/decrement'
11 | const INCREASE_BY = 'post/increaseBy'
12 | const DECREASE_BY = 'post/decreaseBy'
13 |
14 | function reducer(state = initialState, action) {
15 | switch (action.type) {
16 | case INCREMENT:
17 | return { ...state, post: state.post + 1 }
18 | case DECREMENT:
19 | return { ...state, post: state.post - 1 }
20 | case INCREASE_BY:
21 | return { ...state, post: state.post + action.payload }
22 | case DECREASE_BY:
23 | return { ...state, post: state.post - action.payload }
24 | default:
25 | return state
26 | }
27 | }
28 |
29 | const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__?.())
30 |
31 | console.log(store)
32 |
33 | store.subscribe(() => {
34 | console.log(store.getState())
35 | })
36 |
37 |
38 | store.dispatch({ type: INCREMENT })
39 | store.dispatch({ type: DECREMENT })
40 | store.dispatch({ type: INCREASE_BY, payload: 15 })
41 | store.dispatch({ type: DECREASE_BY, payload: 5 })
--------------------------------------------------------------------------------
/02_redux-devtools/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
12 | Redux Intro & Reducer Pattern
13 |
14 | Post:
15 |
16 |
17 |
--------------------------------------------------------------------------------
/02_redux-devtools/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "00_redux-intro-and-reducer-pattern",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "redux": "^4.2.1"
13 | }
14 | },
15 | "node_modules/@babel/runtime": {
16 | "version": "7.23.1",
17 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz",
18 | "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==",
19 | "dependencies": {
20 | "regenerator-runtime": "^0.14.0"
21 | },
22 | "engines": {
23 | "node": ">=6.9.0"
24 | }
25 | },
26 | "node_modules/redux": {
27 | "version": "4.2.1",
28 | "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
29 | "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
30 | "dependencies": {
31 | "@babel/runtime": "^7.9.2"
32 | }
33 | },
34 | "node_modules/regenerator-runtime": {
35 | "version": "0.14.0",
36 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
37 | "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/02_redux-devtools/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "redux": "^4.2.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/02_redux-devtools/script.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux'
2 | const postCountElement = document.querySelector('.post-count')
3 |
4 | const initialState = {
5 | post: 0,
6 | name: 'Anurag Singh',
7 | age: 26,
8 | }
9 |
10 | const INCREMENT = 'post/increment'
11 | const DECREMENT = 'post/decrement'
12 | const INCREASE_BY = 'post/increaseBy'
13 | const DECREASE_BY = 'post/decreaseBy'
14 |
15 | function reducer(state = initialState, action) {
16 | switch (action.type) {
17 | case INCREMENT:
18 | return { ...state, post: state.post + 1 }
19 | case DECREMENT:
20 | return { ...state, post: state.post - 1 }
21 | case INCREASE_BY:
22 | return { ...state, post: state.post + action.payload }
23 | case DECREASE_BY:
24 | return { ...state, post: state.post - action.payload }
25 | default:
26 | return state
27 | }
28 | }
29 |
30 | const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__?.())
31 |
32 | console.log(store)
33 |
34 | const unsubscribe = store.subscribe(() => {
35 | console.log(store.getState())
36 | postCountElement.innerText = store.getState().post
37 | })
38 |
39 | postCountElement.innerText = store.getState().post
40 |
41 | store.dispatch({ type: INCREMENT })
42 | store.dispatch({ type: DECREMENT })
43 | store.dispatch({ type: INCREASE_BY, payload: 15 })
44 | store.dispatch({ type: DECREASE_BY, payload: 5 })
45 |
46 | // unsubscribe()
47 |
48 | postCountElement.addEventListener('click', () => {
49 | store.dispatch({ type: INCREMENT })
50 | })
51 |
--------------------------------------------------------------------------------
/03_make-your-own-redux/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
12 | Redux Intro & Reducer Pattern
13 |
14 | Post:
15 |
16 |
17 |
--------------------------------------------------------------------------------
/03_make-your-own-redux/my-redux.js:
--------------------------------------------------------------------------------
1 | export function myCreateStore(reducer) {
2 | let state
3 | const listeners = []
4 | const store = {
5 | getState() {
6 | return state
7 | },
8 | dispatch(action) {
9 | state = reducer(state, action)
10 | listeners.forEach((listener) => {
11 | listener()
12 | })
13 | },
14 | subscribe(listener) {
15 | listeners.push(listener)
16 | return function () {
17 | const listenerIndex = listeners.findIndex(
18 | (registeredListeners) => registeredListeners === listener
19 | )
20 | listeners.splice(listenerIndex, 1)
21 | }
22 | },
23 | }
24 |
25 | store.dispatch({ type: '@@INIT' })
26 | return store
27 | }
28 |
--------------------------------------------------------------------------------
/03_make-your-own-redux/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "00_redux-intro-and-reducer-pattern",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "redux": "^4.2.1"
13 | }
14 | },
15 | "node_modules/@babel/runtime": {
16 | "version": "7.23.1",
17 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz",
18 | "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==",
19 | "dependencies": {
20 | "regenerator-runtime": "^0.14.0"
21 | },
22 | "engines": {
23 | "node": ">=6.9.0"
24 | }
25 | },
26 | "node_modules/redux": {
27 | "version": "4.2.1",
28 | "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
29 | "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
30 | "dependencies": {
31 | "@babel/runtime": "^7.9.2"
32 | }
33 | },
34 | "node_modules/regenerator-runtime": {
35 | "version": "0.14.0",
36 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
37 | "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/03_make-your-own-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "redux": "^4.2.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/04_manage-complex-state-using-redux/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
12 | Redux Intro & Reducer Pattern
13 |
14 |
15 |
--------------------------------------------------------------------------------
/04_manage-complex-state-using-redux/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "00_redux-intro-and-reducer-pattern",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "redux": "^4.2.1"
13 | }
14 | },
15 | "node_modules/@babel/runtime": {
16 | "version": "7.23.1",
17 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz",
18 | "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==",
19 | "dependencies": {
20 | "regenerator-runtime": "^0.14.0"
21 | },
22 | "engines": {
23 | "node": ">=6.9.0"
24 | }
25 | },
26 | "node_modules/redux": {
27 | "version": "4.2.1",
28 | "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
29 | "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
30 | "dependencies": {
31 | "@babel/runtime": "^7.9.2"
32 | }
33 | },
34 | "node_modules/regenerator-runtime": {
35 | "version": "0.14.0",
36 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
37 | "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/04_manage-complex-state-using-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "redux": "^4.2.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/05_creating-multiple-reducers/cartReducer.js:
--------------------------------------------------------------------------------
1 | export const CART_ADD_ITEM = 'cart/addItem'
2 | export const CART_REMOVE_ITEM = 'cart/removeItem'
3 | export const CART_ITEM_INCREASE_QUANTITY = 'cart/increaseItemQuantity'
4 | export const CART_ITEM_DECREASE_QUANTITY = 'cart/decreaseItemQuantity'
5 |
6 | export default function cartReducer(state = [], action) {
7 | switch (action.type) {
8 | case CART_ADD_ITEM:
9 | return [...state, action.payload]
10 | case CART_REMOVE_ITEM:
11 | return state.filter(
12 | (cartItem) => cartItem.productId !== action.payload.productId
13 | )
14 | case CART_ITEM_INCREASE_QUANTITY:
15 | return state.map((cartItem) => {
16 | if (cartItem.productId === action.payload.productId) {
17 | return { ...cartItem, quantity: cartItem.quantity + 1 }
18 | }
19 | return cartItem
20 | })
21 |
22 | case CART_ITEM_DECREASE_QUANTITY:
23 | return state
24 | .map((cartItem) => {
25 | if (cartItem.productId === action.payload.productId) {
26 | return { ...cartItem, quantity: cartItem.quantity - 1 }
27 | }
28 | return cartItem
29 | })
30 | .filter((cartItem) => cartItem.quantity > 0)
31 | default:
32 | return state
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/05_creating-multiple-reducers/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
12 | Redux Intro & Reducer Pattern
13 |
14 |
15 |
--------------------------------------------------------------------------------
/05_creating-multiple-reducers/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "00_redux-intro-and-reducer-pattern",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "redux": "^4.2.1"
13 | }
14 | },
15 | "node_modules/@babel/runtime": {
16 | "version": "7.23.1",
17 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz",
18 | "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==",
19 | "dependencies": {
20 | "regenerator-runtime": "^0.14.0"
21 | },
22 | "engines": {
23 | "node": ">=6.9.0"
24 | }
25 | },
26 | "node_modules/redux": {
27 | "version": "4.2.1",
28 | "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
29 | "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
30 | "dependencies": {
31 | "@babel/runtime": "^7.9.2"
32 | }
33 | },
34 | "node_modules/regenerator-runtime": {
35 | "version": "0.14.0",
36 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
37 | "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/05_creating-multiple-reducers/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "redux": "^4.2.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/05_creating-multiple-reducers/productsReducer.js:
--------------------------------------------------------------------------------
1 | import { productsList } from './productsList'
2 |
3 | export default function productsReducer(state = productsList) {
4 | return state
5 | }
6 |
--------------------------------------------------------------------------------
/05_creating-multiple-reducers/script.js:
--------------------------------------------------------------------------------
1 | import { combineReducers, createStore } from 'redux'
2 | import productsReducer from './productsReducer'
3 | import cartReducer, {
4 | CART_ADD_ITEM,
5 | CART_ITEM_DECREASE_QUANTITY,
6 | CART_ITEM_INCREASE_QUANTITY,
7 | } from './cartReducer'
8 | import wishListReducer, {
9 | WISHLIST_ADD_ITEM,
10 | WISHLIST_REMOVE_ITEM,
11 | } from './wishListReducer'
12 |
13 | const reducer = combineReducers({
14 | products: productsReducer,
15 | cartItems: cartReducer,
16 | wishList: wishListReducer,
17 | })
18 |
19 | const store = createStore(
20 | reducer,
21 | window.__REDUX_DEVTOOLS_EXTENSION__?.()
22 | )
23 |
24 | console.log(store)
25 |
26 | store.dispatch({ type: CART_ADD_ITEM, payload: { productId: 1, quantity: 1 } })
27 | store.dispatch({ type: CART_ADD_ITEM, payload: { productId: 12, quantity: 1 } })
28 | store.dispatch({
29 | type: CART_ITEM_INCREASE_QUANTITY,
30 | payload: { productId: 12 },
31 | })
32 | store.dispatch({
33 | type: CART_ITEM_DECREASE_QUANTITY,
34 | payload: { productId: 12 },
35 | })
36 |
37 | store.dispatch({
38 | type: CART_ITEM_DECREASE_QUANTITY,
39 | payload: { productId: 12 },
40 | })
41 |
42 | store.dispatch({ type: WISHLIST_ADD_ITEM, payload: { productId: 18 } })
43 | store.dispatch({ type: WISHLIST_ADD_ITEM, payload: { productId: 11 } })
44 | store.dispatch({ type: WISHLIST_REMOVE_ITEM, payload: { productId: 11 } })
45 | store.dispatch({ type: WISHLIST_REMOVE_ITEM, payload: { productId: 18 } })
46 | console.log(store.getState())
47 |
--------------------------------------------------------------------------------
/05_creating-multiple-reducers/wishListReducer.js:
--------------------------------------------------------------------------------
1 | export const WISHLIST_ADD_ITEM = 'wishList/addItem'
2 | export const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
3 |
4 | export default function wishListReducer(state = [], action) {
5 | switch (action.type) {
6 | case WISHLIST_ADD_ITEM:
7 | return [...state, action.payload]
8 |
9 | case WISHLIST_REMOVE_ITEM:
10 | return state.filter(
11 | (wishListItem) => wishListItem.productId !== action.payload.productId
12 | )
13 | default:
14 | return state
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/06_make-your-own-combineReducer-function/cartReducer.js:
--------------------------------------------------------------------------------
1 | export const CART_ADD_ITEM = 'cart/addItem'
2 | export const CART_REMOVE_ITEM = 'cart/removeItem'
3 | export const CART_ITEM_INCREASE_QUANTITY = 'cart/increaseItemQuantity'
4 | export const CART_ITEM_DECREASE_QUANTITY = 'cart/decreaseItemQuantity'
5 |
6 | export default function cartReducer(state = [], action) {
7 | switch (action.type) {
8 | case CART_ADD_ITEM:
9 | return [...state, action.payload]
10 | case CART_REMOVE_ITEM:
11 | return state.filter(
12 | (cartItem) => cartItem.productId !== action.payload.productId
13 | )
14 | case CART_ITEM_INCREASE_QUANTITY:
15 | return state.map((cartItem) => {
16 | if (cartItem.productId === action.payload.productId) {
17 | return { ...cartItem, quantity: cartItem.quantity + 1 }
18 | }
19 | return cartItem
20 | })
21 |
22 | case CART_ITEM_DECREASE_QUANTITY:
23 | return state
24 | .map((cartItem) => {
25 | if (cartItem.productId === action.payload.productId) {
26 | return { ...cartItem, quantity: cartItem.quantity - 1 }
27 | }
28 | return cartItem
29 | })
30 | .filter((cartItem) => cartItem.quantity > 0)
31 | default:
32 | return state
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/06_make-your-own-combineReducer-function/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
12 | Redux Intro & Reducer Pattern
13 |
14 |
15 |
--------------------------------------------------------------------------------
/06_make-your-own-combineReducer-function/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "00_redux-intro-and-reducer-pattern",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "redux": "^4.2.1"
13 | }
14 | },
15 | "node_modules/@babel/runtime": {
16 | "version": "7.23.1",
17 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz",
18 | "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==",
19 | "dependencies": {
20 | "regenerator-runtime": "^0.14.0"
21 | },
22 | "engines": {
23 | "node": ">=6.9.0"
24 | }
25 | },
26 | "node_modules/redux": {
27 | "version": "4.2.1",
28 | "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
29 | "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
30 | "dependencies": {
31 | "@babel/runtime": "^7.9.2"
32 | }
33 | },
34 | "node_modules/regenerator-runtime": {
35 | "version": "0.14.0",
36 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
37 | "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/06_make-your-own-combineReducer-function/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "redux": "^4.2.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/06_make-your-own-combineReducer-function/productsReducer.js:
--------------------------------------------------------------------------------
1 | import { productsList } from './productsList'
2 |
3 | export default function productsReducer(state = productsList) {
4 | return state
5 | }
6 |
--------------------------------------------------------------------------------
/06_make-your-own-combineReducer-function/wishListReducer.js:
--------------------------------------------------------------------------------
1 | export const WISHLIST_ADD_ITEM = 'wishList/addItem'
2 | export const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
3 |
4 | export default function wishListReducer(state = [], action) {
5 | switch (action.type) {
6 | case WISHLIST_ADD_ITEM:
7 | return [...state, action.payload]
8 |
9 | case WISHLIST_REMOVE_ITEM:
10 | return state.filter(
11 | (wishListItem) => wishListItem.productId !== action.payload.productId
12 | )
13 | default:
14 | return state
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/07_action-creators-in-redux/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
12 | Redux Intro & Reducer Pattern
13 |
14 |
15 |
--------------------------------------------------------------------------------
/07_action-creators-in-redux/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "00_redux-intro-and-reducer-pattern",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "redux": "^4.2.1"
13 | }
14 | },
15 | "node_modules/@babel/runtime": {
16 | "version": "7.23.1",
17 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz",
18 | "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==",
19 | "dependencies": {
20 | "regenerator-runtime": "^0.14.0"
21 | },
22 | "engines": {
23 | "node": ">=6.9.0"
24 | }
25 | },
26 | "node_modules/redux": {
27 | "version": "4.2.1",
28 | "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
29 | "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
30 | "dependencies": {
31 | "@babel/runtime": "^7.9.2"
32 | }
33 | },
34 | "node_modules/regenerator-runtime": {
35 | "version": "0.14.0",
36 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
37 | "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/07_action-creators-in-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "redux": "^4.2.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/07_action-creators-in-redux/productsReducer.js:
--------------------------------------------------------------------------------
1 | import { productsList } from './productsList'
2 |
3 | export default function productsReducer(state = productsList) {
4 | return state
5 | }
6 |
--------------------------------------------------------------------------------
/07_action-creators-in-redux/script.js:
--------------------------------------------------------------------------------
1 | import { combineReducers, createStore } from 'redux'
2 | import productsReducer from './productsReducer'
3 | import cartReducer, {
4 | addCartItem,
5 | decreaseCartItemQuantity,
6 | increaseCartItemQuantity,
7 | } from './cartReducer'
8 | import wishListReducer, {
9 | addWishListItem,
10 | removeWishListItem,
11 | } from './wishListReducer'
12 |
13 | const reducer = combineReducers({
14 | products: productsReducer,
15 | cartItems: cartReducer,
16 | wishList: wishListReducer,
17 | })
18 |
19 | const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__?.())
20 |
21 | console.log(store)
22 |
23 | store.dispatch(addCartItem(1))
24 | store.dispatch(addCartItem(12))
25 |
26 | store.dispatch(increaseCartItemQuantity(12))
27 |
28 | store.dispatch(decreaseCartItemQuantity(12))
29 | store.dispatch(decreaseCartItemQuantity(12))
30 |
31 | store.dispatch(addWishListItem(18))
32 | store.dispatch(addWishListItem(11))
33 |
34 | store.dispatch(removeWishListItem(11))
35 | store.dispatch(removeWishListItem(18))
36 |
--------------------------------------------------------------------------------
/07_action-creators-in-redux/wishListReducer.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/08_connect-redux-with-react/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Product from './components/Product'
3 |
4 | import './App.css'
5 | import { useSelector } from 'react-redux'
6 |
7 | export default function App() {
8 | const productsList = useSelector((state) => state.products)
9 | return (
10 |
11 | {productsList.map(({ id, title, rating, price, image }) => (
12 |
19 | ))}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/08_connect-redux-with-react/components/Product.js:
--------------------------------------------------------------------------------
1 | export default function Product({ title, rating, price, imageUrl }) {
2 | return (
3 |
4 |
5 |

6 |
7 |
12 |
13 |
{+rating} ★ ★ ★ ★
14 |
${price}
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/08_connect-redux-with-react/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/08_connect-redux-with-react/main.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 | import { Provider } from 'react-redux'
4 | import { store } from './store'
5 |
6 | createRoot(document.querySelector('#root')).render(
7 |
8 |
9 |
10 | )
11 |
--------------------------------------------------------------------------------
/08_connect-redux-with-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "start": "parcel index.html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-redux": "^8.1.3",
16 | "redux": "^4.2.1"
17 | },
18 | "devDependencies": {
19 | "process": "^0.11.10"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/08_connect-redux-with-react/required-files/Product.js:
--------------------------------------------------------------------------------
1 | export default function Product({ title, rating, price, imageUrl }) {
2 | return (
3 |
4 |
5 |

6 |
7 |
12 |
13 |
{+rating} ★ ★ ★ ★
14 |
{price}
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/08_connect-redux-with-react/store/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers, createStore } from 'redux'
2 | import productsReducer from './productsReducer'
3 | import cartReducer, {
4 | addCartItem,
5 | decreaseCartItemQuantity,
6 | increaseCartItemQuantity,
7 | } from './cartReducer'
8 | import wishListReducer, {
9 | addWishListItem,
10 | removeWishListItem,
11 | } from './wishListReducer'
12 |
13 | const reducer = combineReducers({
14 | products: productsReducer,
15 | cartItems: cartReducer,
16 | wishList: wishListReducer,
17 | })
18 |
19 | export const store = createStore(
20 | reducer,
21 | window.__REDUX_DEVTOOLS_EXTENSION__?.()
22 | )
23 |
24 | // console.log(store)
25 |
26 | // store.dispatch(addCartItem(1))
27 | // store.dispatch(addCartItem(12))
28 |
29 | // store.dispatch(increaseCartItemQuantity(12))
30 |
31 | // store.dispatch(decreaseCartItemQuantity(12))
32 | // store.dispatch(decreaseCartItemQuantity(12))
33 |
34 | // store.dispatch(addWishListItem(18))
35 | // store.dispatch(addWishListItem(11))
36 |
37 | // store.dispatch(removeWishListItem(11))
38 | // store.dispatch(removeWishListItem(18))
39 |
--------------------------------------------------------------------------------
/08_connect-redux-with-react/store/productsReducer.js:
--------------------------------------------------------------------------------
1 | import { productsList } from './productsList'
2 |
3 | export default function productsReducer(state = productsList) {
4 | return state
5 | }
6 |
--------------------------------------------------------------------------------
/08_connect-redux-with-react/store/wishListReducer.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/final-code/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './components/Header'
3 | import { Outlet } from 'react-router-dom'
4 |
5 | import './App.css'
6 |
7 | export default function App() {
8 | return (
9 | <>
10 |
11 |
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/final-code/assets/cart-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/final-code/components/CartItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch } from 'react-redux'
3 | import { decreaseCartItemQuantity, increaseCartItemQuantity } from '../store/cartReducer'
4 |
5 | export default function CartItem({
6 | productId,
7 | title,
8 | rating,
9 | price,
10 | imageUrl,
11 | quantity,
12 | }) {
13 | const dispatch = useDispatch()
14 | return (
15 |
16 |
17 |

18 |
19 |
{title}
20 |
{rating} ★ ★ ★ ★
21 |
22 |
23 |
${price}
24 |
25 |
28 | {quantity}
29 |
32 |
33 |
${quantity * price}
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/final-code/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import CartIcon from '../assets/cart-icon.svg'
4 | import { useSelector } from 'react-redux'
5 |
6 | export default function Header() {
7 | const cartItems = useSelector((state) => state.cartItems)
8 | console.log(cartItems)
9 | return (
10 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/final-code/components/Product.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from 'react-redux'
2 | import { addCartItem } from '../store/cartReducer'
3 |
4 | export default function Product({ productId, title, rating, price, imageUrl }) {
5 | const dispatch = useDispatch()
6 | return (
7 |
8 |
9 |

10 |
11 |
16 |
17 |
{+rating} ★ ★ ★ ★
18 |
${price}
19 |
20 |
21 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/final-code/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/final-code/main.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 | import { Provider } from 'react-redux'
4 | import { store } from './store'
5 | import { RouterProvider, createBrowserRouter } from 'react-router-dom'
6 | import Home from './pages/Home'
7 | import Cart from './pages/Cart'
8 |
9 | const router = createBrowserRouter([
10 | {
11 | path: '/',
12 | element: ,
13 | children: [
14 | {
15 | path: '/',
16 | element: ,
17 | },
18 | {
19 | path: '/cart',
20 | element: ,
21 | },
22 | ],
23 | },
24 | ])
25 |
26 | createRoot(document.querySelector('#root')).render(
27 |
28 |
29 |
30 | )
31 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/final-code/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "start": "parcel index.html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-redux": "^8.1.3",
16 | "react-router-dom": "^6.17.0",
17 | "redux": "^4.2.1"
18 | },
19 | "devDependencies": {
20 | "process": "^0.11.10"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/final-code/pages/Cart.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import CartItem from '../components/CartItem'
3 | import { useSelector } from 'react-redux'
4 |
5 | export default function Cart() {
6 | const cartItems = useSelector((state) => state.cartItems)
7 | return (
8 |
9 |
Items in Your Cart
10 |
11 |
12 |
Item
13 |
Price
14 |
Quantity
15 |
Total
16 |
17 | {cartItems.map(
18 | ({ productId, title, rating, price, imageUrl, quantity }) => (
19 |
28 | )
29 | )}
30 |
31 |
32 |
33 |
34 |
35 | $
36 | {cartItems.reduce(
37 | (accumulator, currentItem) =>
38 | accumulator + currentItem.quantity * currentItem.price,
39 | 0
40 | )}
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/final-code/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import Product from '../components/Product'
4 |
5 | export default function Home() {
6 | const productsList = useSelector((state) => state.products)
7 | return (
8 |
9 | {productsList.map(({ id, title, rating, price, image }) => (
10 |
18 | ))}
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/final-code/store/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers, createStore } from 'redux'
2 | import productsReducer from './productsReducer'
3 | import cartReducer, {
4 | addCartItem,
5 | decreaseCartItemQuantity,
6 | increaseCartItemQuantity,
7 | } from './cartReducer'
8 | import wishListReducer, {
9 | addWishListItem,
10 | removeWishListItem,
11 | } from './wishListReducer'
12 |
13 | const reducer = combineReducers({
14 | products: productsReducer,
15 | cartItems: cartReducer,
16 | wishList: wishListReducer,
17 | })
18 |
19 | export const store = createStore(
20 | reducer,
21 | window.__REDUX_DEVTOOLS_EXTENSION__?.()
22 | )
23 |
24 | // console.log(store)
25 |
26 | // store.dispatch(addCartItem(1))
27 | // store.dispatch(addCartItem(12))
28 |
29 | // store.dispatch(increaseCartItemQuantity(12))
30 |
31 | // store.dispatch(decreaseCartItemQuantity(12))
32 | // store.dispatch(decreaseCartItemQuantity(12))
33 |
34 | // store.dispatch(addWishListItem(18))
35 | // store.dispatch(addWishListItem(11))
36 |
37 | // store.dispatch(removeWishListItem(11))
38 | // store.dispatch(removeWishListItem(18))
39 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/final-code/store/productsReducer.js:
--------------------------------------------------------------------------------
1 | import { productsList } from './productsList'
2 |
3 | export default function productsReducer(state = productsList) {
4 | return state
5 | }
--------------------------------------------------------------------------------
/09_make-shopping-cart/final-code/store/wishListReducer.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/starter-code/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './components/Header'
3 | import { Outlet } from 'react-router-dom'
4 |
5 | import './App.css'
6 |
7 | export default function App() {
8 | return (
9 | <>
10 |
11 |
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/starter-code/assets/cart-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/starter-code/components/CartItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function CartItem({ title, rating, price, imageUrl, quantity }) {
4 | return (
5 |
6 |
7 |

8 |
9 |
{title}
10 |
{rating} ★ ★ ★ ★
11 |
12 |
13 |
${price}
14 |
15 |
16 | {quantity}
17 |
18 |
19 |
${quantity * price}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/starter-code/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import CartIcon from '../assets/cart-icon.svg'
4 |
5 | export default function Header() {
6 | return (
7 |
8 |
9 |
10 | Shopee
11 |
12 |
13 |

14 |
0
15 |
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/starter-code/components/Product.js:
--------------------------------------------------------------------------------
1 | export default function Product({ title, rating, price, imageUrl }) {
2 | return (
3 |
4 |
5 |

6 |
7 |
12 |
13 |
{+rating} ★ ★ ★ ★
14 |
${price}
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/starter-code/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/starter-code/main.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 | import { Provider } from 'react-redux'
4 | import { store } from './store'
5 | import { RouterProvider, createBrowserRouter } from 'react-router-dom'
6 | import Home from './pages/Home'
7 | import Cart from './pages/Cart'
8 |
9 | const router = createBrowserRouter([
10 | {
11 | path: '/',
12 | element: ,
13 | children: [
14 | {
15 | path: '/',
16 | element: ,
17 | },
18 | {
19 | path: '/cart',
20 | element: ,
21 | },
22 | ],
23 | },
24 | ])
25 |
26 | createRoot(document.querySelector('#root')).render(
27 |
28 |
29 |
30 | )
31 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/starter-code/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "start": "parcel index.html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-redux": "^8.1.3",
16 | "react-router-dom": "^6.17.0",
17 | "redux": "^4.2.1"
18 | },
19 | "devDependencies": {
20 | "process": "^0.11.10"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/starter-code/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import Product from '../components/Product'
4 |
5 | export default function Home() {
6 | const productsList = useSelector((state) => state.products)
7 | return (
8 |
9 | {productsList.map(({ id, title, rating, price, image }) => (
10 |
17 | ))}
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/starter-code/store/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers, createStore } from 'redux'
2 | import productsReducer from './productsReducer'
3 | import cartReducer, {
4 | addCartItem,
5 | decreaseCartItemQuantity,
6 | increaseCartItemQuantity,
7 | } from './cartReducer'
8 | import wishListReducer, {
9 | addWishListItem,
10 | removeWishListItem,
11 | } from './wishListReducer'
12 |
13 | const reducer = combineReducers({
14 | products: productsReducer,
15 | cartItems: cartReducer,
16 | wishList: wishListReducer,
17 | })
18 |
19 | export const store = createStore(
20 | reducer,
21 | window.__REDUX_DEVTOOLS_EXTENSION__?.()
22 | )
23 |
24 | // console.log(store)
25 |
26 | // store.dispatch(addCartItem(1))
27 | // store.dispatch(addCartItem(12))
28 |
29 | // store.dispatch(increaseCartItemQuantity(12))
30 |
31 | // store.dispatch(decreaseCartItemQuantity(12))
32 | // store.dispatch(decreaseCartItemQuantity(12))
33 |
34 | // store.dispatch(addWishListItem(18))
35 | // store.dispatch(addWishListItem(11))
36 |
37 | // store.dispatch(removeWishListItem(11))
38 | // store.dispatch(removeWishListItem(18))
39 |
--------------------------------------------------------------------------------
/09_make-shopping-cart/starter-code/store/productsReducer.js:
--------------------------------------------------------------------------------
1 | import { productsList } from './productsList'
2 |
3 | export default function productsReducer(state = productsList) {
4 | return state
5 | }
--------------------------------------------------------------------------------
/09_make-shopping-cart/starter-code/store/wishListReducer.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/10_make-your-own-react-redux/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './components/Header'
3 | import { Outlet } from 'react-router-dom'
4 |
5 | import './App.css'
6 |
7 | export default function App() {
8 | return (
9 | <>
10 |
11 |
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/10_make-your-own-react-redux/assets/cart-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/10_make-your-own-react-redux/components/CartItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch } from '../react-redux'
3 | import {
4 | decreaseCartItemQuantity,
5 | increaseCartItemQuantity,
6 | } from '../store/cartReducer'
7 |
8 | export default function CartItem({
9 | productId,
10 | title,
11 | rating,
12 | price,
13 | imageUrl,
14 | quantity,
15 | }) {
16 | const dispatch = useDispatch()
17 | return (
18 |
19 |
20 |

21 |
22 |
{title}
23 |
{rating} ★ ★ ★ ★
24 |
25 |
26 |
${price}
27 |
28 |
31 | {quantity}
32 |
35 |
36 |
${quantity * price}
37 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/10_make-your-own-react-redux/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import CartIcon from '../assets/cart-icon.svg'
4 | import { useSelector } from '../react-redux'
5 |
6 | export default function Header() {
7 | const cartItems = useSelector((state) => state.cartItems)
8 | return (
9 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/10_make-your-own-react-redux/components/Product.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from '../react-redux'
2 | import { addCartItem } from '../store/cartReducer'
3 |
4 | export default function Product({ productId, title, rating, price, imageUrl }) {
5 | const dispatch = useDispatch()
6 | return (
7 |
8 |
9 |

10 |
11 |
16 |
17 |
{+rating} ★ ★ ★ ★
18 |
${price}
19 |
20 |
21 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/10_make-your-own-react-redux/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/10_make-your-own-react-redux/main.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 | import { Provider } from './react-redux'
4 | import { store } from './store'
5 | import { RouterProvider, createBrowserRouter } from 'react-router-dom'
6 | import Home from './pages/Home'
7 | import Cart from './pages/Cart'
8 |
9 | const router = createBrowserRouter([
10 | {
11 | path: '/',
12 | element: ,
13 | children: [
14 | {
15 | path: '/',
16 | element: ,
17 | },
18 | {
19 | path: '/cart',
20 | element: ,
21 | },
22 | ],
23 | },
24 | ])
25 |
26 | createRoot(document.querySelector('#root')).render(
27 |
28 |
29 |
30 | )
31 |
--------------------------------------------------------------------------------
/10_make-your-own-react-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "start": "parcel index.html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-router-dom": "^6.17.0",
16 | "redux": "^4.2.1"
17 | },
18 | "devDependencies": {
19 | "process": "^0.11.10"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/10_make-your-own-react-redux/pages/Cart.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import CartItem from '../components/CartItem'
3 | import { useSelector } from '../react-redux'
4 |
5 | export default function Cart() {
6 | const cartItems = useSelector((state) => state.cartItems)
7 | return (
8 |
9 |
Items in Your Cart
10 |
11 |
12 |
Item
13 |
Price
14 |
Quantity
15 |
Total
16 |
17 | {cartItems.map(
18 | ({ productId, title, rating, price, imageUrl, quantity }) => (
19 |
28 | )
29 | )}
30 |
31 |
32 |
33 |
34 |
35 | $
36 | {cartItems.reduce(
37 | (accumulator, currentItem) =>
38 | accumulator + currentItem.quantity * currentItem.price,
39 | 0
40 | )}
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/10_make-your-own-react-redux/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from '../react-redux'
3 | import Product from '../components/Product'
4 |
5 | export default function Home() {
6 | const productsList = useSelector((state) => state.products)
7 | return (
8 |
9 | {productsList.map(({ id, title, rating, price, image }) => (
10 |
18 | ))}
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/10_make-your-own-react-redux/react-redux.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from 'react'
2 |
3 | const StoreContext = createContext()
4 |
5 | export function Provider({ children, store }) {
6 | const [state, setState] = useState(store.getState())
7 | useEffect(() => {
8 | store.subscribe(() => {
9 | setState(store.getState())
10 | })
11 | }, [])
12 |
13 | return (
14 |
15 | {children}
16 |
17 | )
18 | }
19 |
20 | export const useDispatch = () => useContext(StoreContext).dispatch
21 | export const useSelector = (selector) =>
22 | selector(useContext(StoreContext).state)
23 |
--------------------------------------------------------------------------------
/10_make-your-own-react-redux/store/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers, createStore } from 'redux'
2 | import productsReducer from './productsReducer'
3 | import cartReducer, {
4 | addCartItem,
5 | decreaseCartItemQuantity,
6 | increaseCartItemQuantity,
7 | } from './cartReducer'
8 | import wishListReducer, {
9 | addWishListItem,
10 | removeWishListItem,
11 | } from './wishListReducer'
12 |
13 | const reducer = combineReducers({
14 | products: productsReducer,
15 | cartItems: cartReducer,
16 | wishList: wishListReducer,
17 | })
18 |
19 | export const store = createStore(
20 | reducer,
21 | window.__REDUX_DEVTOOLS_EXTENSION__?.()
22 | )
23 |
24 | // console.log(store)
25 |
26 | // store.dispatch(addCartItem(1))
27 | // store.dispatch(addCartItem(12))
28 |
29 | // store.dispatch(increaseCartItemQuantity(12))
30 |
31 | // store.dispatch(decreaseCartItemQuantity(12))
32 | // store.dispatch(decreaseCartItemQuantity(12))
33 |
34 | // store.dispatch(addWishListItem(18))
35 | // store.dispatch(addWishListItem(11))
36 |
37 | // store.dispatch(removeWishListItem(11))
38 | // store.dispatch(removeWishListItem(18))
39 |
--------------------------------------------------------------------------------
/10_make-your-own-react-redux/store/productsReducer.js:
--------------------------------------------------------------------------------
1 | import { productsList } from './productsList'
2 |
3 | export default function productsReducer(state = productsList) {
4 | return state
5 | }
--------------------------------------------------------------------------------
/10_make-your-own-react-redux/store/wishListReducer.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/11_what-are-slices-in-redux/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './components/Header'
3 | import { Outlet } from 'react-router-dom'
4 |
5 | import './App.css'
6 |
7 | export default function App() {
8 | return (
9 | <>
10 |
11 |
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/11_what-are-slices-in-redux/assets/cart-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/11_what-are-slices-in-redux/components/CartItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch } from '../react-redux'
3 | import {
4 | decreaseCartItemQuantity,
5 | increaseCartItemQuantity,
6 | } from '../store/slices/cartSlice'
7 |
8 | export default function CartItem({
9 | productId,
10 | title,
11 | rating,
12 | price,
13 | imageUrl,
14 | quantity,
15 | }) {
16 | const dispatch = useDispatch()
17 | return (
18 |
19 |
20 |

21 |
22 |
{title}
23 |
{rating} ★ ★ ★ ★
24 |
25 |
26 |
${price}
27 |
28 |
31 | {quantity}
32 |
35 |
36 |
${quantity * price}
37 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/11_what-are-slices-in-redux/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import CartIcon from '../assets/cart-icon.svg'
4 | import { useSelector } from '../react-redux'
5 |
6 | export default function Header() {
7 | const cartItems = useSelector((state) => state.cartItems)
8 | return (
9 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/11_what-are-slices-in-redux/components/Product.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from '../react-redux'
2 | import { addCartItem } from '../store/slices/cartSlice'
3 |
4 | export default function Product({ productId, title, rating, price, imageUrl }) {
5 | const dispatch = useDispatch()
6 | return (
7 |
8 |
9 |

10 |
11 |
16 |
17 |
{+rating} ★ ★ ★ ★
18 |
${price}
19 |
20 |
21 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/11_what-are-slices-in-redux/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/11_what-are-slices-in-redux/main.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 | import { Provider } from './react-redux'
4 | import { store } from './store'
5 | import { RouterProvider, createBrowserRouter } from 'react-router-dom'
6 | import Home from './pages/Home'
7 | import Cart from './pages/Cart'
8 |
9 | const router = createBrowserRouter([
10 | {
11 | path: '/',
12 | element: ,
13 | children: [
14 | {
15 | path: '/',
16 | element: ,
17 | },
18 | {
19 | path: '/cart',
20 | element: ,
21 | },
22 | ],
23 | },
24 | ])
25 |
26 | createRoot(document.querySelector('#root')).render(
27 |
28 |
29 |
30 | )
31 |
--------------------------------------------------------------------------------
/11_what-are-slices-in-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "start": "parcel index.html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-router-dom": "^6.17.0",
16 | "redux": "^4.2.1"
17 | },
18 | "devDependencies": {
19 | "process": "^0.11.10"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/11_what-are-slices-in-redux/pages/Cart.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import CartItem from '../components/CartItem'
3 | import { useSelector } from '../react-redux'
4 |
5 | export default function Cart() {
6 | const cartItems = useSelector((state) => state.cartItems)
7 | return (
8 |
9 |
Items in Your Cart
10 |
11 |
12 |
Item
13 |
Price
14 |
Quantity
15 |
Total
16 |
17 | {cartItems.map(
18 | ({ productId, title, rating, price, imageUrl, quantity }) => (
19 |
28 | )
29 | )}
30 |
31 |
32 |
33 |
34 |
35 | $
36 | {cartItems.reduce(
37 | (accumulator, currentItem) =>
38 | accumulator + currentItem.quantity * currentItem.price,
39 | 0
40 | )}
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/11_what-are-slices-in-redux/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from '../react-redux'
3 | import Product from '../components/Product'
4 |
5 | export default function Home() {
6 | const productsList = useSelector((state) => state.products)
7 | // useSelector(console.log)
8 | return (
9 |
10 | {productsList.map(({ id, title, rating, price, image }) => (
11 |
19 | ))}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/11_what-are-slices-in-redux/react-redux.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from 'react'
2 |
3 | const StoreContext = createContext()
4 |
5 | export function Provider({ children, store }) {
6 | const [state, setState] = useState(store.getState())
7 | useEffect(() => {
8 | store.subscribe(() => {
9 | setState(store.getState())
10 | })
11 | }, [])
12 |
13 | return (
14 |
15 | {children}
16 |
17 | )
18 | }
19 |
20 | export const useDispatch = () => useContext(StoreContext).dispatch
21 | export const useSelector = (selector) =>
22 | selector(useContext(StoreContext).state)
23 |
--------------------------------------------------------------------------------
/11_what-are-slices-in-redux/store/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers, createStore } from 'redux'
2 | import productsReducer from './slices/productsSlice'
3 | import cartReducer from './slices/cartSlice'
4 | import wishListReducer from './slices/wishListSlice'
5 |
6 | const reducer = combineReducers({
7 | products: productsReducer,
8 | cartItems: cartReducer,
9 | wishList: wishListReducer,
10 | })
11 |
12 | export const store = createStore(
13 | reducer,
14 | window.__REDUX_DEVTOOLS_EXTENSION__?.()
15 | )
16 |
--------------------------------------------------------------------------------
/11_what-are-slices-in-redux/store/slices/productsSlice.js:
--------------------------------------------------------------------------------
1 | import { productsList } from '../productsList'
2 |
3 | export default function productsReducer(state = productsList) {
4 | return state
5 | }
6 |
--------------------------------------------------------------------------------
/11_what-are-slices-in-redux/store/slices/wishListSlice.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/12_immerJS-in-redux/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './components/Header'
3 | import { Outlet } from 'react-router-dom'
4 |
5 | import './App.css'
6 |
7 | export default function App() {
8 | return (
9 | <>
10 |
11 |
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/12_immerJS-in-redux/assets/cart-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/12_immerJS-in-redux/components/CartItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch } from '../react-redux'
3 | import {
4 | decreaseCartItemQuantity,
5 | increaseCartItemQuantity,
6 | removeCartItem,
7 | } from '../store/slices/cartSlice'
8 |
9 | export default function CartItem({
10 | productId,
11 | title,
12 | rating,
13 | price,
14 | imageUrl,
15 | quantity,
16 | }) {
17 | const dispatch = useDispatch()
18 | return (
19 |
20 |
21 |

22 |
23 |
{title}
24 |
{rating} ★ ★ ★ ★
25 |
26 |
27 |
${price}
28 |
29 |
32 | {quantity}
33 |
36 |
43 |
44 |
${quantity * price}
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/12_immerJS-in-redux/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import CartIcon from '../assets/cart-icon.svg'
4 | import { useSelector } from '../react-redux'
5 |
6 | export default function Header() {
7 | const cartItems = useSelector((state) => state.cartItems)
8 | return (
9 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/12_immerJS-in-redux/components/Product.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from '../react-redux'
2 | import { addCartItem } from '../store/slices/cartSlice'
3 |
4 | export default function Product({ productId, title, rating, price, imageUrl }) {
5 | const dispatch = useDispatch()
6 | return (
7 |
8 |
9 |

10 |
11 |
16 |
17 |
{+rating} ★ ★ ★ ★
18 |
${price}
19 |
20 |
21 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/12_immerJS-in-redux/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/12_immerJS-in-redux/main.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 | import { Provider } from './react-redux'
4 | import { store } from './store'
5 | import { RouterProvider, createBrowserRouter } from 'react-router-dom'
6 | import Home from './pages/Home'
7 | import Cart from './pages/Cart'
8 |
9 | const router = createBrowserRouter([
10 | {
11 | path: '/',
12 | element: ,
13 | children: [
14 | {
15 | path: '/',
16 | element: ,
17 | },
18 | {
19 | path: '/cart',
20 | element: ,
21 | },
22 | ],
23 | },
24 | ])
25 |
26 | createRoot(document.querySelector('#root')).render(
27 |
28 |
29 |
30 | )
31 |
--------------------------------------------------------------------------------
/12_immerJS-in-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "start": "parcel index.html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "immer": "^10.0.3",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-router-dom": "^6.17.0",
17 | "redux": "^4.2.1"
18 | },
19 | "devDependencies": {
20 | "process": "^0.11.10"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/12_immerJS-in-redux/pages/Cart.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import CartItem from '../components/CartItem'
3 | import { useSelector } from '../react-redux'
4 |
5 | export default function Cart() {
6 | const cartItems = useSelector((state) => state.cartItems)
7 | return (
8 |
9 |
Items in Your Cart
10 |
11 |
12 |
Item
13 |
Price
14 |
Quantity
15 |
Total
16 |
17 | {cartItems.map(
18 | ({ productId, title, rating, price, imageUrl, quantity }) => (
19 |
28 | )
29 | )}
30 |
31 |
32 |
33 |
34 |
35 | $
36 | {cartItems.reduce(
37 | (accumulator, currentItem) =>
38 | accumulator + currentItem.quantity * currentItem.price,
39 | 0
40 | )}
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/12_immerJS-in-redux/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from '../react-redux'
3 | import Product from '../components/Product'
4 |
5 | export default function Home() {
6 | const productsList = useSelector((state) => state.products)
7 | // useSelector(console.log)
8 | return (
9 |
10 | {productsList.map(({ id, title, rating, price, image }) => (
11 |
19 | ))}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/12_immerJS-in-redux/react-redux.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from 'react'
2 |
3 | const StoreContext = createContext()
4 |
5 | export function Provider({ children, store }) {
6 | const [state, setState] = useState(store.getState())
7 | useEffect(() => {
8 | store.subscribe(() => {
9 | setState(store.getState())
10 | })
11 | }, [])
12 |
13 | return (
14 |
15 | {children}
16 |
17 | )
18 | }
19 |
20 | export const useDispatch = () => useContext(StoreContext).dispatch
21 | export const useSelector = (selector) =>
22 | selector(useContext(StoreContext).state)
23 |
--------------------------------------------------------------------------------
/12_immerJS-in-redux/store/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers, createStore } from 'redux'
2 | import productsReducer from './slices/productsSlice'
3 | import cartReducer from './slices/cartSlice'
4 | import wishListReducer from './slices/wishListSlice'
5 |
6 | const reducer = combineReducers({
7 | products: productsReducer,
8 | cartItems: cartReducer,
9 | wishList: wishListReducer,
10 | })
11 |
12 | export const store = createStore(
13 | reducer,
14 | window.__REDUX_DEVTOOLS_EXTENSION__?.()
15 | )
16 |
17 | const users = [
18 | {
19 | name: 'Anurag',
20 | age: 26,
21 | },
22 | {
23 | name: 'Ram',
24 | age: 18,
25 | },
26 | {
27 | name: 'Adarsh',
28 | age: 16,
29 | },
30 | ]
--------------------------------------------------------------------------------
/12_immerJS-in-redux/store/slices/productsSlice.js:
--------------------------------------------------------------------------------
1 | import { productsList } from '../productsList'
2 |
3 | export default function productsReducer(state = productsList) {
4 | return state
5 | }
6 |
--------------------------------------------------------------------------------
/12_immerJS-in-redux/store/slices/wishListSlice.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/13_redux-toolkit/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './components/Header'
3 | import { Outlet } from 'react-router-dom'
4 |
5 | import './App.css'
6 |
7 | export default function App() {
8 | return (
9 | <>
10 |
11 |
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/13_redux-toolkit/assets/cart-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/13_redux-toolkit/components/CartItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch } from '../react-redux'
3 | import {
4 | decreaseCartItemQuantity,
5 | increaseCartItemQuantity,
6 | removeCartItem,
7 | } from '../store/slices/cartSlice'
8 |
9 | export default function CartItem({
10 | productId,
11 | title,
12 | rating,
13 | price,
14 | imageUrl,
15 | quantity,
16 | }) {
17 | const dispatch = useDispatch()
18 | return (
19 |
20 |
21 |

22 |
23 |
{title}
24 |
{rating} ★ ★ ★ ★
25 |
26 |
27 |
${price}
28 |
29 |
32 | {quantity}
33 |
36 |
43 |
44 |
${quantity * price}
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/13_redux-toolkit/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import CartIcon from '../assets/cart-icon.svg'
4 | import { useSelector } from '../react-redux'
5 |
6 | export default function Header() {
7 | const cartItems = useSelector((state) => state.cartItems)
8 | return (
9 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/13_redux-toolkit/components/Product.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from '../react-redux'
2 | import { addCartItem } from '../store/slices/cartSlice'
3 |
4 | export default function Product({ productId, title, rating, price, imageUrl }) {
5 | const dispatch = useDispatch()
6 | return (
7 |
8 |
9 |

10 |
11 |
16 |
17 |
{+rating} ★ ★ ★ ★
18 |
${price}
19 |
20 |
21 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/13_redux-toolkit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/13_redux-toolkit/main.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 | import { Provider } from './react-redux'
4 | import { store } from './store'
5 | import { RouterProvider, createBrowserRouter } from 'react-router-dom'
6 | import Home from './pages/Home'
7 | import Cart from './pages/Cart'
8 |
9 | const router = createBrowserRouter([
10 | {
11 | path: '/',
12 | element: ,
13 | children: [
14 | {
15 | path: '/',
16 | element: ,
17 | },
18 | {
19 | path: '/cart',
20 | element: ,
21 | },
22 | ],
23 | },
24 | ])
25 |
26 | createRoot(document.querySelector('#root')).render(
27 |
28 |
29 |
30 | )
31 |
--------------------------------------------------------------------------------
/13_redux-toolkit/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "start": "parcel index.html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^1.9.7",
14 | "immer": "^10.0.3",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0",
17 | "react-router-dom": "^6.17.0"
18 | },
19 | "devDependencies": {
20 | "process": "^0.11.10"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/13_redux-toolkit/pages/Cart.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import CartItem from '../components/CartItem'
3 | import { useSelector } from '../react-redux'
4 |
5 | export default function Cart() {
6 | const cartItems = useSelector((state) => state.cartItems)
7 | return (
8 |
9 |
Items in Your Cart
10 |
11 |
12 |
Item
13 |
Price
14 |
Quantity
15 |
Total
16 |
17 | {cartItems.map(
18 | ({ productId, title, rating, price, imageUrl, quantity }) => (
19 |
28 | )
29 | )}
30 |
31 |
32 |
33 |
34 |
35 | $
36 | {cartItems.reduce(
37 | (accumulator, currentItem) =>
38 | accumulator + currentItem.quantity * currentItem.price,
39 | 0
40 | )}
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/13_redux-toolkit/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from '../react-redux'
3 | import Product from '../components/Product'
4 |
5 | export default function Home() {
6 | const productsList = useSelector((state) => state.products)
7 | // useSelector(console.log)
8 | return (
9 |
10 | {productsList.map(({ id, title, rating, price, image }) => (
11 |
19 | ))}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/13_redux-toolkit/react-redux.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from 'react'
2 |
3 | const StoreContext = createContext()
4 |
5 | export function Provider({ children, store }) {
6 | const [state, setState] = useState(store.getState())
7 | useEffect(() => {
8 | store.subscribe(() => {
9 | setState(store.getState())
10 | })
11 | }, [])
12 |
13 | return (
14 |
15 | {children}
16 |
17 | )
18 | }
19 |
20 | export const useDispatch = () => useContext(StoreContext).dispatch
21 | export const useSelector = (selector) =>
22 | selector(useContext(StoreContext).state)
23 |
--------------------------------------------------------------------------------
/13_redux-toolkit/store/index.js:
--------------------------------------------------------------------------------
1 | import productsReducer from './slices/productsSlice'
2 | import cartReducer from './slices/cartSlice'
3 | import wishListReducer from './slices/wishListSlice'
4 | import { configureStore } from '@reduxjs/toolkit'
5 |
6 | export const store = configureStore({
7 | reducer: {
8 | products: productsReducer,
9 | cartItems: cartReducer,
10 | wishList: wishListReducer,
11 | },
12 | })
13 |
--------------------------------------------------------------------------------
/13_redux-toolkit/store/slices/cartSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit'
2 |
3 | const findItemIndex = (state, action) =>
4 | state.findIndex((cartItem) => cartItem.productId === action.payload.productId)
5 |
6 | const slice = createSlice({
7 | name: 'cart',
8 | initialState: [],
9 | reducers: {
10 | addCartItem(state, action) {
11 | const existingItemIndex = findItemIndex(state, action)
12 | if (existingItemIndex !== -1) state[existingItemIndex].quantity += 1
13 | else state.push({ ...action.payload, quantity: 1 })
14 | },
15 | removeCartItem(state, action) {
16 | const existingItemIndex = findItemIndex(state, action)
17 | state.splice(existingItemIndex, 1)
18 | },
19 | increaseCartItemQuantity(state, action) {
20 | const existingItemIndex = findItemIndex(state, action)
21 | state[existingItemIndex].quantity += 1
22 | },
23 | decreaseCartItemQuantity(state, action) {
24 | const existingItemIndex = findItemIndex(state, action)
25 | state[existingItemIndex].quantity -= 1
26 | if (state[existingItemIndex].quantity === 0)
27 | state.splice(existingItemIndex, 1)
28 | },
29 | },
30 | })
31 | export const {
32 | addCartItem,
33 | removeCartItem,
34 | increaseCartItemQuantity,
35 | decreaseCartItemQuantity,
36 | } = slice.actions
37 |
38 | export default slice.reducer
39 |
--------------------------------------------------------------------------------
/13_redux-toolkit/store/slices/productsSlice.js:
--------------------------------------------------------------------------------
1 | import { productsList } from '../productsList'
2 |
3 | export default function productsReducer(state = productsList) {
4 | return state
5 | }
6 |
--------------------------------------------------------------------------------
/13_redux-toolkit/store/slices/wishListSlice.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './components/Header'
3 | import { Outlet } from 'react-router-dom'
4 |
5 | import './App.css'
6 |
7 | export default function App() {
8 | return (
9 | <>
10 |
11 |
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/assets/cart-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/components/CartItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch } from '../react-redux'
3 | import {
4 | decreaseCartItemQuantity,
5 | increaseCartItemQuantity,
6 | removeCartItem,
7 | } from '../store/slices/cartSlice'
8 |
9 | export default function CartItem({
10 | productId,
11 | title,
12 | rating,
13 | price,
14 | imageUrl,
15 | quantity,
16 | }) {
17 | const dispatch = useDispatch()
18 | return (
19 |
20 |
21 |

22 |
23 |
{title}
24 |
{rating} ★ ★ ★ ★
25 |
26 |
27 |
${price}
28 |
29 |
32 | {quantity}
33 |
36 |
43 |
44 |
${quantity * price}
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import CartIcon from '../assets/cart-icon.svg'
4 | import { useSelector } from '../react-redux'
5 |
6 | export default function Header() {
7 | const cartItems = useSelector((state) => state.cartItems)
8 | return (
9 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/components/Product.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from '../react-redux'
2 | import { addCartItem } from '../store/slices/cartSlice'
3 |
4 | export default function Product({ productId, title, rating, price, imageUrl }) {
5 | const dispatch = useDispatch()
6 | return (
7 |
8 |
9 |

10 |
11 |
16 |
17 |
{+rating} ★ ★ ★ ★
18 |
${price}
19 |
20 |
21 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/main.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 | import { Provider } from './react-redux'
4 | import { store } from './store'
5 | import { RouterProvider, createBrowserRouter } from 'react-router-dom'
6 | import Home from './pages/Home'
7 | import Cart from './pages/Cart'
8 |
9 | const router = createBrowserRouter([
10 | {
11 | path: '/',
12 | element: ,
13 | children: [
14 | {
15 | path: '/',
16 | element: ,
17 | },
18 | {
19 | path: '/cart',
20 | element: ,
21 | },
22 | ],
23 | },
24 | ])
25 |
26 | createRoot(document.querySelector('#root')).render(
27 |
28 |
29 |
30 | )
31 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "start": "parcel index.html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^1.9.7",
14 | "immer": "^10.0.3",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0",
17 | "react-router-dom": "^6.17.0"
18 | },
19 | "devDependencies": {
20 | "process": "^0.11.10"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from '../react-redux'
3 | import Product from '../components/Product'
4 |
5 | export default function Home() {
6 | const productsList = useSelector((state) => state.products)
7 | // useSelector(console.log)
8 | return (
9 |
10 | {productsList.map(({ id, title, rating, price, image }) => (
11 |
19 | ))}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/react-redux.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from 'react'
2 |
3 | const StoreContext = createContext()
4 |
5 | export function Provider({ children, store }) {
6 | const [state, setState] = useState(store.getState())
7 | useEffect(() => {
8 | store.subscribe(() => {
9 | setState(store.getState())
10 | })
11 | }, [])
12 |
13 | return (
14 |
15 | {children}
16 |
17 | )
18 | }
19 |
20 | export const useDispatch = () => useContext(StoreContext).dispatch
21 | export const useSelector = (selector) =>
22 | selector(useContext(StoreContext).state)
23 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/redux-toolkit.js:
--------------------------------------------------------------------------------
1 | import { produce } from 'immer'
2 |
3 | export function myCreateSlice(config) {
4 | const { name, initialState, reducers } = config
5 | const actions = {}
6 |
7 | Object.keys(reducers).forEach((key) => {
8 | actions[key] = function (payload) {
9 | return {
10 | type: `${name}/${key}`,
11 | payload,
12 | }
13 | }
14 | })
15 |
16 | function reducer(originalState = initialState, action) {
17 | return produce(originalState, (state) => {
18 | const caseReducer = reducers[action.type.split('/')[1]]
19 | if (caseReducer) return caseReducer(state, action)
20 | return state
21 | })
22 | }
23 | return { actions, reducer }
24 | }
25 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/store/index.js:
--------------------------------------------------------------------------------
1 | import productsReducer from './slices/productsSlice'
2 | import cartReducer from './slices/cartSlice'
3 | import wishListReducer from './slices/wishListSlice'
4 | import { configureStore } from '@reduxjs/toolkit'
5 |
6 | export const store = configureStore({
7 | reducer: {
8 | products: productsReducer,
9 | cartItems: cartReducer,
10 | wishList: wishListReducer,
11 | },
12 | })
13 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/store/slices/cartSlice.js:
--------------------------------------------------------------------------------
1 | import { myCreateSlice } from '../../redux-toolkit'
2 |
3 | const findItemIndex = (state, action) =>
4 | state.findIndex((cartItem) => cartItem.productId === action.payload.productId)
5 |
6 | const mySlice = myCreateSlice({
7 | name: 'cart',
8 | initialState: [],
9 | reducers: {
10 | addCartItem(state, action) {
11 | const existingItemIndex = findItemIndex(state, action)
12 | if (existingItemIndex !== -1) state[existingItemIndex].quantity += 1
13 | else state.push({ ...action.payload, quantity: 1 })
14 | },
15 | removeCartItem(state, action) {
16 | const existingItemIndex = findItemIndex(state, action)
17 | state.splice(existingItemIndex, 1)
18 | },
19 | increaseCartItemQuantity(state, action) {
20 | const existingItemIndex = findItemIndex(state, action)
21 | state[existingItemIndex].quantity += 1
22 | },
23 | decreaseCartItemQuantity(state, action) {
24 | const existingItemIndex = findItemIndex(state, action)
25 | state[existingItemIndex].quantity -= 1
26 | if (state[existingItemIndex].quantity === 0)
27 | state.splice(existingItemIndex, 1)
28 | },
29 | },
30 | })
31 |
32 | export const {
33 | addCartItem,
34 | removeCartItem,
35 | increaseCartItemQuantity,
36 | decreaseCartItemQuantity,
37 | } = mySlice.actions
38 |
39 | console.log(addCartItem())
40 |
41 | export default mySlice.reducer
42 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/store/slices/productsSlice.js:
--------------------------------------------------------------------------------
1 | import { productsList } from '../productsList'
2 |
3 | export default function productsReducer(state = productsList) {
4 | return state
5 | }
6 |
--------------------------------------------------------------------------------
/14_make-your-own-redux-toolkit/store/slices/wishListSlice.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './components/Header'
3 | import { Outlet } from 'react-router-dom'
4 |
5 | import './App.css'
6 |
7 | export default function App() {
8 | return (
9 | <>
10 |
11 |
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/assets/cart-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/components/CartItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch } from '../react-redux'
3 | import {
4 | decreaseCartItemQuantity,
5 | increaseCartItemQuantity,
6 | removeCartItem,
7 | } from '../store/slices/cartSlice'
8 |
9 | export default function CartItem({
10 | productId,
11 | title,
12 | rating,
13 | price,
14 | imageUrl,
15 | quantity,
16 | }) {
17 | const dispatch = useDispatch()
18 | return (
19 |
20 |
21 |

22 |
23 |
{title}
24 |
{rating} ★ ★ ★ ★
25 |
26 |
27 |
${price}
28 |
29 |
32 | {quantity}
33 |
36 |
43 |
44 |
${quantity * price}
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import CartIcon from '../assets/cart-icon.svg'
4 | import { useSelector } from '../react-redux'
5 |
6 | export default function Header() {
7 | const cartItems = useSelector((state) => state.cartItems)
8 | return (
9 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/components/Product.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from '../react-redux'
2 | import { addCartItem } from '../store/slices/cartSlice'
3 |
4 | export default function Product({ productId, title, rating, price, imageUrl }) {
5 | const dispatch = useDispatch()
6 | return (
7 |
8 |
9 |

10 |
11 |
16 |
17 |
{+rating} ★ ★ ★ ★
18 |
${price}
19 |
20 |
21 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/main.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 | import { Provider } from './react-redux'
4 | import { store } from './store'
5 | import { RouterProvider, createBrowserRouter } from 'react-router-dom'
6 | import Home from './pages/Home'
7 | import Cart from './pages/Cart'
8 |
9 | const router = createBrowserRouter([
10 | {
11 | path: '/',
12 | element: ,
13 | children: [
14 | {
15 | path: '/',
16 | element: ,
17 | },
18 | {
19 | path: '/cart',
20 | element: ,
21 | },
22 | ],
23 | },
24 | ])
25 |
26 | createRoot(document.querySelector('#root')).render(
27 |
28 |
29 |
30 | )
31 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "start": "parcel index.html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^1.9.7",
14 | "immer": "^10.0.3",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0",
17 | "react-router-dom": "^6.17.0"
18 | },
19 | "devDependencies": {
20 | "process": "^0.11.10"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/pages/Cart.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import CartItem from '../components/CartItem'
3 | import { useSelector } from '../react-redux'
4 |
5 | export default function Cart() {
6 | const cartItems = useSelector((state) => state.cartItems)
7 | return (
8 |
9 |
Items in Your Cart
10 |
11 |
12 |
Item
13 |
Price
14 |
Quantity
15 |
Total
16 |
17 | {cartItems.map(
18 | ({ productId, title, rating, price, imageUrl, quantity }) => (
19 |
28 | )
29 | )}
30 |
31 |
32 |
33 |
34 |
35 | $
36 | {cartItems.reduce(
37 | (accumulator, currentItem) =>
38 | accumulator + currentItem.quantity * currentItem.price,
39 | 0
40 | )}
41 |
42 |
43 |
44 |
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from '../react-redux'
3 | import Product from '../components/Product'
4 |
5 | export default function Home() {
6 | const productsList = useSelector((state) => state.products)
7 | // useSelector(console.log)
8 | return (
9 |
10 | {productsList.map(({ id, title, rating, price, image }) => (
11 |
19 | ))}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/react-redux.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from 'react'
2 |
3 | const StoreContext = createContext()
4 |
5 | export function Provider({ children, store }) {
6 | const [state, setState] = useState(store.getState())
7 | useEffect(() => {
8 | store.subscribe(() => {
9 | setState(store.getState())
10 | })
11 | }, [])
12 |
13 | return (
14 |
15 | {children}
16 |
17 | )
18 | }
19 |
20 | export const useDispatch = () => useContext(StoreContext).dispatch
21 | export const useSelector = (selector) =>
22 | selector(useContext(StoreContext).state)
23 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/redux-toolkit.js:
--------------------------------------------------------------------------------
1 | import { produce } from 'immer'
2 |
3 | export function myCreateSlice(config) {
4 | const { name, initialState, reducers } = config
5 | const actions = {}
6 |
7 | Object.keys(reducers).forEach((key) => {
8 | actions[key] = function (payload) {
9 | return {
10 | type: `${name}/${key}`,
11 | payload,
12 | }
13 | }
14 | })
15 |
16 | function reducer(originalState = initialState, action) {
17 | return produce(originalState, (state) => {
18 | const caseReducer = reducers[action.type.split('/')[1]]
19 | if (caseReducer) return caseReducer(state, action)
20 | return state
21 | })
22 | }
23 | return { actions, reducer }
24 | }
25 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/store/index.js:
--------------------------------------------------------------------------------
1 | import productsReducer from './slices/productsSlice'
2 | import cartReducer from './slices/cartSlice'
3 | import wishListReducer from './slices/wishListSlice'
4 | import { configureStore } from '@reduxjs/toolkit'
5 | import { logger } from './middleware/logger'
6 |
7 |
8 | export const store = configureStore({
9 | reducer: {
10 | products: productsReducer,
11 | cartItems: cartReducer,
12 | wishList: wishListReducer,
13 | },
14 | middleware: [logger, ],
15 | })
16 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/store/middleware/logger.js:
--------------------------------------------------------------------------------
1 | export const logger = (store) => (next) => (action) => {
2 | console.log('store: ', store)
3 | console.log('next: ', next)
4 | console.log('action: ', action)
5 | next(action)
6 | }
7 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/store/slices/cartSlice.js:
--------------------------------------------------------------------------------
1 | import { myCreateSlice } from '../../redux-toolkit'
2 |
3 | const findItemIndex = (state, action) =>
4 | state.findIndex((cartItem) => cartItem.productId === action.payload.productId)
5 |
6 | const mySlice = myCreateSlice({
7 | name: 'cart',
8 | initialState: [],
9 | reducers: {
10 | addCartItem(state, action) {
11 | const existingItemIndex = findItemIndex(state, action)
12 | if (existingItemIndex !== -1) state[existingItemIndex].quantity += 1
13 | else state.push({ ...action.payload, quantity: 1 })
14 | },
15 | removeCartItem(state, action) {
16 | const existingItemIndex = findItemIndex(state, action)
17 | state.splice(existingItemIndex, 1)
18 | },
19 | increaseCartItemQuantity(state, action) {
20 | const existingItemIndex = findItemIndex(state, action)
21 | state[existingItemIndex].quantity += 1
22 | },
23 | decreaseCartItemQuantity(state, action) {
24 | const existingItemIndex = findItemIndex(state, action)
25 | state[existingItemIndex].quantity -= 1
26 | if (state[existingItemIndex].quantity === 0)
27 | state.splice(existingItemIndex, 1)
28 | },
29 | },
30 | })
31 |
32 | export const {
33 | addCartItem,
34 | removeCartItem,
35 | increaseCartItemQuantity,
36 | decreaseCartItemQuantity,
37 | } = mySlice.actions
38 |
39 | export default mySlice.reducer
40 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/store/slices/productsSlice.js:
--------------------------------------------------------------------------------
1 | import { productsList } from '../productsList'
2 |
3 | export default function productsReducer(state = productsList) {
4 | return state
5 | }
6 |
--------------------------------------------------------------------------------
/15_middlewares-in-redux/store/slices/wishListSlice.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/16_making-api-calls-in-redux/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './components/Header'
3 | import { Outlet } from 'react-router-dom'
4 |
5 | import './App.css'
6 |
7 | export default function App() {
8 | return (
9 | <>
10 |
11 |
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/16_making-api-calls-in-redux/assets/cart-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/16_making-api-calls-in-redux/components/CartItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch } from 'react-redux'
3 | import {
4 | decreaseCartItemQuantity,
5 | increaseCartItemQuantity,
6 | removeCartItem,
7 | } from '../store/slices/cartSlice'
8 |
9 | export default function CartItem({
10 | productId,
11 | title,
12 | rating,
13 | price,
14 | imageUrl,
15 | quantity,
16 | }) {
17 | const dispatch = useDispatch()
18 | return (
19 |
20 |
21 |

22 |
23 |
{title}
24 |
{rating} ★ ★ ★ ★
25 |
26 |
27 |
${price}
28 |
29 |
32 | {quantity}
33 |
36 |
43 |
44 |
${quantity * price}
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/16_making-api-calls-in-redux/components/Product.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from 'react-redux'
2 | import { addCartItem } from '../store/slices/cartSlice'
3 |
4 | export default function Product({ productId, title, rating, price, imageUrl }) {
5 | const dispatch = useDispatch()
6 | return (
7 |
8 |
9 |

10 |
11 |
16 |
17 |
{+rating} ★ ★ ★ ★
18 |
${price}
19 |
20 |
21 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/16_making-api-calls-in-redux/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/16_making-api-calls-in-redux/main.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 | import { Provider } from 'react-redux'
4 | import { store } from './store'
5 | import { RouterProvider, createBrowserRouter } from 'react-router-dom'
6 | import Home from './pages/Home'
7 | import Cart from './pages/Cart'
8 |
9 | const router = createBrowserRouter([
10 | {
11 | path: '/',
12 | element: ,
13 | children: [
14 | {
15 | path: '/',
16 | element: ,
17 | },
18 | {
19 | path: '/cart',
20 | element: ,
21 | },
22 | ],
23 | },
24 | ])
25 |
26 | createRoot(document.querySelector('#root')).render(
27 |
28 |
29 |
30 | )
31 |
--------------------------------------------------------------------------------
/16_making-api-calls-in-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "start": "parcel index.html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^1.9.7",
14 | "immer": "^10.0.3",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0",
17 | "react-redux": "^8.1.3",
18 | "react-router-dom": "^6.17.0"
19 | },
20 | "devDependencies": {
21 | "process": "^0.11.10"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/16_making-api-calls-in-redux/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import Product from '../components/Product'
4 |
5 | export default function Home() {
6 | const productsList = useSelector((state) => state.products.list)
7 | const isLoading = useSelector((state) => state.products.loading)
8 | const error = useSelector((state) => state.products.error)
9 | return isLoading ? (
10 | Loading...
11 | ) : error ? (
12 | {error}
13 | ) : (
14 |
15 | {productsList.map(({ id, title, rating, price, image }) => (
16 |
24 | ))}
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/16_making-api-calls-in-redux/react-redux.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from 'react'
2 |
3 | const StoreContext = createContext()
4 |
5 | export function Provider({ children, store }) {
6 | const [state, setState] = useState(store.getState())
7 | useEffect(() => {
8 | store.subscribe(() => {
9 | setState(store.getState())
10 | })
11 | }, [])
12 |
13 | return (
14 |
15 | {children}
16 |
17 | )
18 | }
19 |
20 | export const useDispatch = () => useContext(StoreContext).dispatch
21 | export const useSelector = (selector) =>
22 | selector(useContext(StoreContext).state)
23 |
--------------------------------------------------------------------------------
/16_making-api-calls-in-redux/redux-toolkit.js:
--------------------------------------------------------------------------------
1 | import { produce } from 'immer'
2 |
3 | export function myCreateSlice(config) {
4 | const { name, initialState, reducers } = config
5 | const actions = {}
6 |
7 | Object.keys(reducers).forEach((key) => {
8 | actions[key] = function (payload) {
9 | return {
10 | type: `${name}/${key}`,
11 | payload,
12 | }
13 | }
14 | })
15 |
16 | function reducer(originalState = initialState, action) {
17 | return produce(originalState, (state) => {
18 | const caseReducer = reducers[action.type.split('/')[1]]
19 | if (caseReducer) return caseReducer(state, action)
20 | return state
21 | })
22 | }
23 | return { actions, reducer }
24 | }
25 |
--------------------------------------------------------------------------------
/16_making-api-calls-in-redux/store/index.js:
--------------------------------------------------------------------------------
1 | import productsReducer from './slices/productsSlice'
2 | import cartReducer from './slices/cartSlice'
3 | import wishListReducer from './slices/wishListSlice'
4 | import { configureStore } from '@reduxjs/toolkit'
5 | import { logger } from './middleware/logger'
6 |
7 |
8 | export const store = configureStore({
9 | reducer: {
10 | products: productsReducer,
11 | cartItems: cartReducer,
12 | wishList: wishListReducer,
13 | },
14 | // middleware: [logger, ],
15 | })
16 |
--------------------------------------------------------------------------------
/16_making-api-calls-in-redux/store/middleware/logger.js:
--------------------------------------------------------------------------------
1 | export const logger = (store) => (next) => (action) => {
2 | console.log('store: ', store)
3 | console.log('next: ', next)
4 | console.log('action: ', action)
5 | next(action)
6 | }
7 |
--------------------------------------------------------------------------------
/16_making-api-calls-in-redux/store/slices/productsSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit'
2 |
3 | const slice = createSlice({
4 | name: 'product',
5 | initialState: {
6 | loading: false,
7 | list: [],
8 | error: '',
9 | },
10 | reducers: {
11 | fetchProducts(state) {
12 | state.loading = true
13 | },
14 | fetchProductsError(state, action) {
15 | state.loading = false
16 | state.error = action.payload || 'Something went wrong!'
17 | },
18 | updateAllProducts(state, action) {
19 | state.loading = false
20 | state.list = action.payload
21 | state.error = ''
22 | },
23 | },
24 | })
25 |
26 | export const { updateAllProducts, fetchProducts, fetchProductsError } = slice.actions
27 | export default slice.reducer
28 |
--------------------------------------------------------------------------------
/16_making-api-calls-in-redux/store/slices/wishListSlice.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/17_selectors-in-redux/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './components/Header'
3 | import { Outlet } from 'react-router-dom'
4 |
5 | import './App.css'
6 |
7 | export default function App() {
8 | return (
9 | <>
10 |
11 |
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/17_selectors-in-redux/assets/cart-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/17_selectors-in-redux/components/CartItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch } from 'react-redux'
3 | import {
4 | decreaseCartItemQuantity,
5 | increaseCartItemQuantity,
6 | removeCartItem,
7 | } from '../store/slices/cartSlice'
8 |
9 | export default function CartItem({
10 | productId,
11 | title,
12 | rating,
13 | price,
14 | imageUrl,
15 | quantity,
16 | }) {
17 | const dispatch = useDispatch()
18 | return (
19 |
20 |
21 |

22 |
23 |
{title}
24 |
{rating} ★ ★ ★ ★
25 |
26 |
27 |
${price}
28 |
29 |
32 | {quantity}
33 |
36 |
43 |
44 |
${quantity * price}
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/17_selectors-in-redux/components/Product.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from 'react-redux'
2 | import { addCartItem } from '../store/slices/cartSlice'
3 |
4 | export default function Product({ productId, title, rating, price, imageUrl }) {
5 | const dispatch = useDispatch()
6 | return (
7 |
8 |
9 |

10 |
11 |
16 |
17 |
{+rating} ★ ★ ★ ★
18 |
${price}
19 |
20 |
21 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/17_selectors-in-redux/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/17_selectors-in-redux/main.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 | import { Provider } from 'react-redux'
4 | import { store } from './store'
5 | import { RouterProvider, createBrowserRouter } from 'react-router-dom'
6 | import Home from './pages/Home'
7 | import Cart from './pages/Cart'
8 |
9 | const router = createBrowserRouter([
10 | {
11 | path: '/',
12 | element: ,
13 | children: [
14 | {
15 | path: '/',
16 | element: ,
17 | },
18 | {
19 | path: '/cart',
20 | element: ,
21 | },
22 | ],
23 | },
24 | ])
25 |
26 | createRoot(document.querySelector('#root')).render(
27 |
28 |
29 |
30 | )
31 |
--------------------------------------------------------------------------------
/17_selectors-in-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "start": "parcel index.html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^1.9.7",
14 | "immer": "^10.0.3",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0",
17 | "react-redux": "^8.1.3",
18 | "react-router-dom": "^6.17.0"
19 | },
20 | "devDependencies": {
21 | "process": "^0.11.10"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/17_selectors-in-redux/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import Product from '../components/Product'
4 | import {
5 | getAllProducts,
6 | getProductError,
7 | getProductLoadingState,
8 | } from '../store/slices/productsSlice'
9 |
10 | export default function Home() {
11 | const productsList = useSelector(getAllProducts)
12 | const isLoading = useSelector(getProductLoadingState)
13 | const error = useSelector(getProductError)
14 | return isLoading ? (
15 | Loading...
16 | ) : error ? (
17 | {error}
18 | ) : (
19 |
20 | {productsList.map(({ id, title, rating, price, image }) => (
21 |
29 | ))}
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/17_selectors-in-redux/react-redux.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from 'react'
2 |
3 | const StoreContext = createContext()
4 |
5 | export function Provider({ children, store }) {
6 | const [state, setState] = useState(store.getState())
7 | useEffect(() => {
8 | store.subscribe(() => {
9 | setState(store.getState())
10 | })
11 | }, [])
12 |
13 | return (
14 |
15 | {children}
16 |
17 | )
18 | }
19 |
20 | export const useDispatch = () => useContext(StoreContext).dispatch
21 | export const useSelector = (selector) =>
22 | selector(useContext(StoreContext).state)
23 |
--------------------------------------------------------------------------------
/17_selectors-in-redux/redux-toolkit.js:
--------------------------------------------------------------------------------
1 | import { produce } from 'immer'
2 |
3 | export function myCreateSlice(config) {
4 | const { name, initialState, reducers } = config
5 | const actions = {}
6 |
7 | Object.keys(reducers).forEach((key) => {
8 | actions[key] = function (payload) {
9 | return {
10 | type: `${name}/${key}`,
11 | payload,
12 | }
13 | }
14 | })
15 |
16 | function reducer(originalState = initialState, action) {
17 | return produce(originalState, (state) => {
18 | const caseReducer = reducers[action.type.split('/')[1]]
19 | if (caseReducer) return caseReducer(state, action)
20 | return state
21 | })
22 | }
23 | return { actions, reducer }
24 | }
25 |
--------------------------------------------------------------------------------
/17_selectors-in-redux/store/index.js:
--------------------------------------------------------------------------------
1 | import productsReducer from './slices/productsSlice'
2 | import cartReducer from './slices/cartSlice'
3 | import wishListReducer from './slices/wishListSlice'
4 | import { configureStore } from '@reduxjs/toolkit'
5 | import { logger } from './middleware/logger'
6 |
7 |
8 | export const store = configureStore({
9 | reducer: {
10 | products: productsReducer,
11 | cartItems: cartReducer,
12 | wishList: wishListReducer,
13 | },
14 | // middleware: [logger, ],
15 | })
16 |
--------------------------------------------------------------------------------
/17_selectors-in-redux/store/middleware/logger.js:
--------------------------------------------------------------------------------
1 | export const logger = (store) => (next) => (action) => {
2 | console.log('store: ', store)
3 | console.log('next: ', next)
4 | console.log('action: ', action)
5 | next(action)
6 | }
7 |
--------------------------------------------------------------------------------
/17_selectors-in-redux/store/slices/productsSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit'
2 |
3 | const slice = createSlice({
4 | name: 'product',
5 | initialState: {
6 | loading: false,
7 | list: [],
8 | error: '',
9 | },
10 | reducers: {
11 | fetchProducts(state) {
12 | state.loading = true
13 | },
14 | fetchProductsError(state, action) {
15 | state.loading = false
16 | state.error = action.payload || 'Something went wrong!'
17 | },
18 | updateAllProducts(state, action) {
19 | state.loading = false
20 | state.list = action.payload
21 | state.error = ''
22 | },
23 | },
24 | })
25 |
26 | export const getAllProducts = (state) => state.products.list
27 | export const getProductLoadingState = (state) => state.products.loading
28 | export const getProductError = (state) => state.products.error
29 |
30 | export const { updateAllProducts, fetchProducts, fetchProductsError } = slice.actions
31 | export default slice.reducer
32 |
--------------------------------------------------------------------------------
/17_selectors-in-redux/store/slices/wishListSlice.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './components/Header'
3 | import { Outlet } from 'react-router-dom'
4 |
5 | import './App.css'
6 |
7 | export default function App() {
8 | return (
9 | <>
10 |
11 |
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/assets/cart-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/components/CartItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch } from 'react-redux'
3 | import {
4 | decreaseCartItemQuantity,
5 | increaseCartItemQuantity,
6 | removeCartItem,
7 | } from '../store/slices/cartSlice'
8 |
9 | export default function CartItem({
10 | productId,
11 | title,
12 | rating,
13 | price,
14 | imageUrl,
15 | quantity,
16 | }) {
17 | const dispatch = useDispatch()
18 | return (
19 |
20 |
21 |

22 |
23 |
{title}
24 |
{rating} ★ ★ ★ ★
25 |
26 |
27 |
${price}
28 |
29 |
32 | {quantity}
33 |
36 |
43 |
44 |
${quantity * price}
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/components/Product.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from 'react-redux'
2 | import { addCartItem } from '../store/slices/cartSlice'
3 |
4 | export default function Product({ productId, title, rating, price, imageUrl }) {
5 | const dispatch = useDispatch()
6 | return (
7 |
8 |
9 |

10 |
11 |
16 |
17 |
{+rating} ★ ★ ★ ★
18 |
${price}
19 |
20 |
21 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/main.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 | import { Provider } from 'react-redux'
4 | import { store } from './store'
5 | import { RouterProvider, createBrowserRouter } from 'react-router-dom'
6 | import Home from './pages/Home'
7 | import Cart from './pages/Cart'
8 |
9 | const router = createBrowserRouter([
10 | {
11 | path: '/',
12 | element: ,
13 | children: [
14 | {
15 | path: '/',
16 | element: ,
17 | },
18 | {
19 | path: '/cart',
20 | element: ,
21 | },
22 | ],
23 | },
24 | ])
25 |
26 | createRoot(document.querySelector('#root')).render(
27 |
28 |
29 |
30 | )
31 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "start": "parcel index.html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^1.9.7",
14 | "immer": "^10.0.3",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0",
17 | "react-redux": "^8.1.3",
18 | "react-router-dom": "^6.17.0"
19 | },
20 | "devDependencies": {
21 | "process": "^0.11.10"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import Product from '../components/Product'
4 | import {
5 | getAllProducts,
6 | getProductError,
7 | getProductLoadingState,
8 | } from '../store/slices/productsSlice'
9 |
10 | export default function Home() {
11 | const productsList = useSelector(getAllProducts)
12 | const isLoading = useSelector(getProductLoadingState)
13 | const error = useSelector(getProductError)
14 | return isLoading ? (
15 | Loading...
16 | ) : error ? (
17 | {error}
18 | ) : (
19 |
20 | {productsList.map(({ id, title, rating, price, image }) => (
21 |
29 | ))}
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/react-redux.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from 'react'
2 |
3 | const StoreContext = createContext()
4 |
5 | export function Provider({ children, store }) {
6 | const [state, setState] = useState(store.getState())
7 | useEffect(() => {
8 | store.subscribe(() => {
9 | setState(store.getState())
10 | })
11 | }, [])
12 |
13 | return (
14 |
15 | {children}
16 |
17 | )
18 | }
19 |
20 | export const useDispatch = () => useContext(StoreContext).dispatch
21 | export const useSelector = (selector) =>
22 | selector(useContext(StoreContext).state)
23 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/redux-toolkit.js:
--------------------------------------------------------------------------------
1 | import { produce } from 'immer'
2 |
3 | export function myCreateSlice(config) {
4 | const { name, initialState, reducers } = config
5 | const actions = {}
6 |
7 | Object.keys(reducers).forEach((key) => {
8 | actions[key] = function (payload) {
9 | return {
10 | type: `${name}/${key}`,
11 | payload,
12 | }
13 | }
14 | })
15 |
16 | function reducer(originalState = initialState, action) {
17 | return produce(originalState, (state) => {
18 | const caseReducer = reducers[action.type.split('/')[1]]
19 | if (caseReducer) return caseReducer(state, action)
20 | return state
21 | })
22 | }
23 | return { actions, reducer }
24 | }
25 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/store/index.js:
--------------------------------------------------------------------------------
1 | import productsReducer from './slices/productsSlice'
2 | import cartReducer from './slices/cartSlice'
3 | import wishListReducer from './slices/wishListSlice'
4 | import { configureStore } from '@reduxjs/toolkit'
5 | import { apiMiddleware } from './middleware/api'
6 |
7 |
8 | export const store = configureStore({
9 | reducer: {
10 | products: productsReducer,
11 | cartItems: cartReducer,
12 | wishList: wishListReducer,
13 | },
14 | middleware: [apiMiddleware],
15 | })
16 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/store/middleware/api.js:
--------------------------------------------------------------------------------
1 | export const apiMiddleware =
2 | ({ dispatch }) =>
3 | (next) =>
4 | (action) => {
5 | const BASE_URL = 'https://fakestoreapi.com'
6 | if (action.type === 'api/makeCall') {
7 | next(action)
8 | const { url, onStart, onSuccess, onError } = action.payload
9 | dispatch({
10 | type: onStart,
11 | })
12 | fetch(`${BASE_URL}/${url}`)
13 | .then((res) => res.json())
14 | .then((data) => {
15 | dispatch({
16 | type: onSuccess,
17 | payload: data,
18 | })
19 | })
20 | .catch(() => {
21 | dispatch({
22 | type: onError,
23 | })
24 | })
25 | } else {
26 | next(action)
27 | }
28 | }
29 |
30 | export const fetchData = (payload) => ({ type: 'api/makeCall', payload })
31 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/store/middleware/logger.js:
--------------------------------------------------------------------------------
1 | export const logger = (store) => (next) => (action) => {
2 | console.log('store: ', store)
3 | console.log('next: ', next)
4 | console.log('action: ', action)
5 | next(action)
6 | }
7 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/store/slices/productsSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit'
2 |
3 | const slice = createSlice({
4 | name: 'product',
5 | initialState: {
6 | loading: false,
7 | list: [],
8 | error: '',
9 | },
10 | reducers: {
11 | fetchProducts(state) {
12 | state.loading = true
13 | },
14 | fetchProductsError(state, action) {
15 | state.loading = false
16 | state.error = action.payload || 'Something went wrong!'
17 | },
18 | updateAllProducts(state, action) {
19 | state.loading = false
20 | state.list = action.payload
21 | state.error = ''
22 | },
23 | },
24 | })
25 |
26 | export const getAllProducts = (state) => state.products.list
27 | export const getProductLoadingState = (state) => state.products.loading
28 | export const getProductError = (state) => state.products.error
29 |
30 | export const { updateAllProducts, fetchProducts, fetchProductsError } = slice.actions
31 | export default slice.reducer
32 |
--------------------------------------------------------------------------------
/18_custom-api-middleware/store/slices/wishListSlice.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/19_redux-thunk/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './components/Header'
3 | import { Outlet } from 'react-router-dom'
4 |
5 | import './App.css'
6 |
7 | export default function App() {
8 | return (
9 | <>
10 |
11 |
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/19_redux-thunk/assets/cart-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/19_redux-thunk/components/CartItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch } from 'react-redux'
3 | import {
4 | decreaseCartItemQuantity,
5 | increaseCartItemQuantity,
6 | removeCartItem,
7 | } from '../store/slices/cartSlice'
8 |
9 | export default function CartItem({
10 | productId,
11 | title,
12 | rating,
13 | price,
14 | imageUrl,
15 | quantity,
16 | }) {
17 | const dispatch = useDispatch()
18 | return (
19 |
20 |
21 |

22 |
23 |
{title}
24 |
{rating} ★ ★ ★ ★
25 |
26 |
27 |
${price}
28 |
29 |
32 | {quantity}
33 |
36 |
43 |
44 |
${quantity * price}
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/19_redux-thunk/components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { Link } from 'react-router-dom'
3 | import CartIcon from '../assets/cart-icon.svg'
4 | import { useDispatch, useSelector } from 'react-redux'
5 | import {
6 | fetchProductsData,
7 | } from '../store/slices/productsSlice'
8 | import {
9 | fetchCartItemsData,
10 | } from '../store/slices/cartSlice'
11 |
12 | export default function Header() {
13 | const dispatch = useDispatch()
14 | useEffect(() => {
15 | dispatch(fetchProductsData())
16 | dispatch(fetchCartItemsData())
17 | }, [])
18 | const cartItems = useSelector((state) => state.cartItems.list)
19 | return (
20 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/19_redux-thunk/components/Product.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from 'react-redux'
2 | import { addCartItem } from '../store/slices/cartSlice'
3 |
4 | export default function Product({ productId, title, rating, price, imageUrl }) {
5 | const dispatch = useDispatch()
6 | return (
7 |
8 |
9 |

10 |
11 |
16 |
17 |
{+rating} ★ ★ ★ ★
18 |
${price}
19 |
20 |
21 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/19_redux-thunk/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/19_redux-thunk/main.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 | import { Provider } from 'react-redux'
4 | import { store } from './store'
5 | import { RouterProvider, createBrowserRouter } from 'react-router-dom'
6 | import Home from './pages/Home'
7 | import Cart from './pages/Cart'
8 |
9 | const router = createBrowserRouter([
10 | {
11 | path: '/',
12 | element: ,
13 | children: [
14 | {
15 | path: '/',
16 | element: ,
17 | },
18 | {
19 | path: '/cart',
20 | element: ,
21 | },
22 | ],
23 | },
24 | ])
25 |
26 | createRoot(document.querySelector('#root')).render(
27 |
28 |
29 |
30 | )
31 |
--------------------------------------------------------------------------------
/19_redux-thunk/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "start": "parcel index.html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^1.9.7",
14 | "immer": "^10.0.3",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0",
17 | "react-redux": "^8.1.3",
18 | "react-router-dom": "^6.17.0"
19 | },
20 | "devDependencies": {
21 | "process": "^0.11.10"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/19_redux-thunk/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import Product from '../components/Product'
4 | import {
5 | getAllProducts,
6 | getProductError,
7 | getProductLoadingState,
8 | } from '../store/slices/productsSlice'
9 |
10 | export default function Home() {
11 | const productsList = useSelector(getAllProducts)
12 | const isLoading = useSelector(getProductLoadingState)
13 | const error = useSelector(getProductError)
14 | return isLoading ? (
15 | Loading...
16 | ) : error ? (
17 | {error}
18 | ) : (
19 |
20 | {productsList.map(({ id, title, rating, price, image }) => (
21 |
29 | ))}
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/19_redux-thunk/react-redux.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from 'react'
2 |
3 | const StoreContext = createContext()
4 |
5 | export function Provider({ children, store }) {
6 | const [state, setState] = useState(store.getState())
7 | useEffect(() => {
8 | store.subscribe(() => {
9 | setState(store.getState())
10 | })
11 | }, [])
12 |
13 | return (
14 |
15 | {children}
16 |
17 | )
18 | }
19 |
20 | export const useDispatch = () => useContext(StoreContext).dispatch
21 | export const useSelector = (selector) =>
22 | selector(useContext(StoreContext).state)
23 |
--------------------------------------------------------------------------------
/19_redux-thunk/redux-toolkit.js:
--------------------------------------------------------------------------------
1 | import { produce } from 'immer'
2 |
3 | export function myCreateSlice(config) {
4 | const { name, initialState, reducers } = config
5 | const actions = {}
6 |
7 | Object.keys(reducers).forEach((key) => {
8 | actions[key] = function (payload) {
9 | return {
10 | type: `${name}/${key}`,
11 | payload,
12 | }
13 | }
14 | })
15 |
16 | function reducer(originalState = initialState, action) {
17 | return produce(originalState, (state) => {
18 | const caseReducer = reducers[action.type.split('/')[1]]
19 | if (caseReducer) return caseReducer(state, action)
20 | return state
21 | })
22 | }
23 | return { actions, reducer }
24 | }
25 |
--------------------------------------------------------------------------------
/19_redux-thunk/store/index.js:
--------------------------------------------------------------------------------
1 | import productsReducer from './slices/productsSlice'
2 | import cartReducer from './slices/cartSlice'
3 | import wishListReducer from './slices/wishListSlice'
4 | import { configureStore } from '@reduxjs/toolkit'
5 | import { apiMiddleware } from './middleware/api'
6 | import { func } from './middleware/func'
7 | import { logger } from './middleware/logger'
8 |
9 | export const store = configureStore({
10 | reducer: {
11 | products: productsReducer,
12 | cartItems: cartReducer,
13 | wishList: wishListReducer,
14 | },
15 | middleware: (getDefaultMiddleware) => [...getDefaultMiddleware(), logger],
16 | })
17 |
--------------------------------------------------------------------------------
/19_redux-thunk/store/middleware/api.js:
--------------------------------------------------------------------------------
1 | export const apiMiddleware =
2 | ({ dispatch }) =>
3 | (next) =>
4 | (action) => {
5 | const BASE_URL = 'https://fakestoreapi.com'
6 | if (action.type === 'api/makeCall') {
7 | next(action)
8 | const { url, onStart, onSuccess, onError } = action.payload
9 | dispatch({
10 | type: onStart,
11 | })
12 | fetch(`${BASE_URL}/${url}`)
13 | .then((res) => res.json())
14 | .then((data) => {
15 | dispatch({
16 | type: onSuccess,
17 | payload: data,
18 | })
19 | })
20 | .catch(() => {
21 | dispatch({
22 | type: onError,
23 | })
24 | })
25 | } else {
26 | next(action)
27 | }
28 | }
29 |
30 | export const fetchData = (payload) => ({ type: 'api/makeCall', payload })
31 |
--------------------------------------------------------------------------------
/19_redux-thunk/store/middleware/func.js:
--------------------------------------------------------------------------------
1 | export const func =
2 | ({ dispatch, getState }) =>
3 | (next) =>
4 | (action) => {
5 | if (typeof action === 'function') {
6 | action(dispatch, getState)
7 | } else {
8 | next(action)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/19_redux-thunk/store/middleware/logger.js:
--------------------------------------------------------------------------------
1 | export const logger = (store) => (next) => (action) => {
2 | console.log('store: ', store)
3 | console.log('next: ', next)
4 | console.log('action: ', action)
5 | next(action)
6 | }
7 |
--------------------------------------------------------------------------------
/19_redux-thunk/store/slices/productsSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit'
2 |
3 | const slice = createSlice({
4 | name: 'product',
5 | initialState: {
6 | loading: false,
7 | list: [],
8 | error: '',
9 | },
10 | reducers: {
11 | fetchProducts(state) {
12 | state.loading = true
13 | },
14 | fetchProductsError(state, action) {
15 | state.loading = false
16 | state.error = action.payload || 'Something went wrong!'
17 | },
18 | updateAllProducts(state, action) {
19 | state.loading = false
20 | state.list = action.payload
21 | state.error = ''
22 | },
23 | },
24 | })
25 |
26 | export const getAllProducts = (state) => state.products.list
27 | export const getProductLoadingState = (state) => state.products.loading
28 | export const getProductError = (state) => state.products.error
29 |
30 |
31 | const { updateAllProducts, fetchProducts, fetchProductsError } = slice.actions
32 |
33 | export const fetchProductsData = () => (dispatch) => {
34 | dispatch(fetchProducts())
35 | fetch(`https://fakestoreapi.com/products`)
36 | .then((res) => res.json())
37 | .then((data) => {
38 | dispatch(updateAllProducts(data))
39 | })
40 | .catch(() => {
41 | dispatch(fetchProductsError())
42 | })
43 | }
44 |
45 | export default slice.reducer
46 |
--------------------------------------------------------------------------------
/19_redux-thunk/store/slices/wishListSlice.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './components/Header'
3 | import { Outlet } from 'react-router-dom'
4 |
5 | import './App.css'
6 |
7 | export default function App() {
8 | return (
9 | <>
10 |
11 |
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/assets/cart-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/components/CartItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useDispatch } from 'react-redux'
3 | import {
4 | decreaseCartItemQuantity,
5 | increaseCartItemQuantity,
6 | removeCartItem,
7 | } from '../store/slices/cartSlice'
8 |
9 | export default function CartItem({
10 | productId,
11 | title,
12 | rating,
13 | price,
14 | imageUrl,
15 | quantity,
16 | }) {
17 | const dispatch = useDispatch()
18 | return (
19 |
20 |
21 |

22 |
23 |
{title}
24 |
{rating} ★ ★ ★ ★
25 |
26 |
27 |
${price}
28 |
29 |
32 | {quantity}
33 |
36 |
43 |
44 |
${quantity * price}
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { Link } from 'react-router-dom'
3 | import CartIcon from '../assets/cart-icon.svg'
4 | import { useDispatch, useSelector } from 'react-redux'
5 | import {
6 | fetchProductsData,
7 | } from '../store/slices/productsSlice'
8 | import {
9 | fetchCartItemsData,
10 | } from '../store/slices/cartSlice'
11 |
12 | export default function Header() {
13 | const dispatch = useDispatch()
14 | useEffect(() => {
15 | dispatch(fetchProductsData())
16 | dispatch(fetchCartItemsData())
17 | }, [])
18 | const cartItems = useSelector((state) => state.cartItems.list)
19 | return (
20 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/components/Product.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from 'react-redux'
2 | import { addCartItem } from '../store/slices/cartSlice'
3 |
4 | export default function Product({ productId, title, rating, price, imageUrl }) {
5 | const dispatch = useDispatch()
6 | return (
7 |
8 |
9 |

10 |
11 |
16 |
17 |
{+rating} ★ ★ ★ ★
18 |
${price}
19 |
20 |
21 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux Intro & Reducer Pattern
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/main.js:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 | import { Provider } from 'react-redux'
4 | import { store } from './store'
5 | import { RouterProvider, createBrowserRouter } from 'react-router-dom'
6 | import Home from './pages/Home'
7 | import Cart from './pages/Cart'
8 |
9 | const router = createBrowserRouter([
10 | {
11 | path: '/',
12 | element: ,
13 | children: [
14 | {
15 | path: '/',
16 | element: ,
17 | },
18 | {
19 | path: '/cart',
20 | element: ,
21 | },
22 | ],
23 | },
24 | ])
25 |
26 | createRoot(document.querySelector('#root')).render(
27 |
28 |
29 |
30 | )
31 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "00_redux-intro-and-reducer-pattern",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "script.js",
6 | "scripts": {
7 | "start": "parcel index.html"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^1.9.7",
14 | "immer": "^10.0.3",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0",
17 | "react-redux": "^8.1.3",
18 | "react-router-dom": "^6.17.0"
19 | },
20 | "devDependencies": {
21 | "process": "^0.11.10"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useSelector } from 'react-redux'
3 | import Product from '../components/Product'
4 | import {
5 | getAllProducts,
6 | getProductError,
7 | getProductLoadingState,
8 | } from '../store/slices/productsSlice'
9 |
10 | export default function Home() {
11 | const productsList = useSelector(getAllProducts)
12 | const isLoading = useSelector(getProductLoadingState)
13 | const error = useSelector(getProductError)
14 | return isLoading ? (
15 | Loading...
16 | ) : error ? (
17 | {error}
18 | ) : (
19 |
20 | {productsList.map(({ id, title, rating, price, image }) => (
21 |
29 | ))}
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/react-redux.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from 'react'
2 |
3 | const StoreContext = createContext()
4 |
5 | export function Provider({ children, store }) {
6 | const [state, setState] = useState(store.getState())
7 | useEffect(() => {
8 | store.subscribe(() => {
9 | setState(store.getState())
10 | })
11 | }, [])
12 |
13 | return (
14 |
15 | {children}
16 |
17 | )
18 | }
19 |
20 | export const useDispatch = () => useContext(StoreContext).dispatch
21 | export const useSelector = (selector) =>
22 | selector(useContext(StoreContext).state)
23 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/redux-toolkit.js:
--------------------------------------------------------------------------------
1 | import { produce } from 'immer'
2 |
3 | export function myCreateSlice(config) {
4 | const { name, initialState, reducers } = config
5 | const actions = {}
6 |
7 | Object.keys(reducers).forEach((key) => {
8 | actions[key] = function (payload) {
9 | return {
10 | type: `${name}/${key}`,
11 | payload,
12 | }
13 | }
14 | })
15 |
16 | function reducer(originalState = initialState, action) {
17 | return produce(originalState, (state) => {
18 | const caseReducer = reducers[action.type.split('/')[1]]
19 | if (caseReducer) return caseReducer(state, action)
20 | return state
21 | })
22 | }
23 | return { actions, reducer }
24 | }
25 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/store/index.js:
--------------------------------------------------------------------------------
1 | import productsReducer from './slices/productsSlice'
2 | import cartReducer from './slices/cartSlice'
3 | import wishListReducer from './slices/wishListSlice'
4 | import { configureStore } from '@reduxjs/toolkit'
5 | import { apiMiddleware } from './middleware/api'
6 | import { func } from './middleware/func'
7 | import { logger } from './middleware/logger'
8 |
9 | export const store = configureStore({
10 | reducer: {
11 | products: productsReducer,
12 | cartItems: cartReducer,
13 | wishList: wishListReducer,
14 | },
15 | middleware: (getDefaultMiddleware) => [...getDefaultMiddleware()],
16 | })
17 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/store/middleware/api.js:
--------------------------------------------------------------------------------
1 | export const apiMiddleware =
2 | ({ dispatch }) =>
3 | (next) =>
4 | (action) => {
5 | const BASE_URL = 'https://fakestoreapi.com'
6 | if (action.type === 'api/makeCall') {
7 | next(action)
8 | const { url, onStart, onSuccess, onError } = action.payload
9 | dispatch({
10 | type: onStart,
11 | })
12 | fetch(`${BASE_URL}/${url}`)
13 | .then((res) => res.json())
14 | .then((data) => {
15 | dispatch({
16 | type: onSuccess,
17 | payload: data,
18 | })
19 | })
20 | .catch(() => {
21 | dispatch({
22 | type: onError,
23 | })
24 | })
25 | } else {
26 | next(action)
27 | }
28 | }
29 |
30 | export const fetchData = (payload) => ({ type: 'api/makeCall', payload })
31 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/store/middleware/func.js:
--------------------------------------------------------------------------------
1 | export const func =
2 | ({ dispatch, getState }) =>
3 | (next) =>
4 | (action) => {
5 | if (typeof action === 'function') {
6 | action(dispatch, getState)
7 | } else {
8 | next(action)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/store/middleware/logger.js:
--------------------------------------------------------------------------------
1 | export const logger = (store) => (next) => (action) => {
2 | console.log('store: ', store)
3 | console.log('next: ', next)
4 | console.log('action: ', action)
5 | next(action)
6 | }
7 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/store/slices/productsSlice.js:
--------------------------------------------------------------------------------
1 | import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
2 |
3 | export const fetchProductsData = createAsyncThunk(
4 | 'product/fetchProductItems',
5 | async () => {
6 | try {
7 | const response = await fetch(`https://fakestoreapi.com/products`)
8 | return response.json()
9 | } catch (err) {
10 | throw err
11 | }
12 | }
13 | )
14 |
15 | const slice = createSlice({
16 | name: 'product',
17 | initialState: {
18 | loading: false,
19 | list: [],
20 | error: '',
21 | },
22 | extraReducers: (builder) => {
23 | builder
24 | .addCase(fetchProductsData.pending, (state) => {
25 | state.loading = true
26 | })
27 | .addCase(fetchProductsData.fulfilled, (state, action) => {
28 | state.loading = false
29 | state.list = action.payload
30 | state.error = ''
31 | })
32 | .addCase(fetchProductsData.rejected, (state, action) => {
33 | state.loading = false
34 | state.error = action.payload || 'Something went wrong!'
35 | })
36 | },
37 | })
38 |
39 | export const getAllProducts = (state) => state.products.list
40 | export const getProductLoadingState = (state) => state.products.loading
41 | export const getProductError = (state) => state.products.error
42 |
43 |
44 |
45 | export default slice.reducer
46 |
--------------------------------------------------------------------------------
/20_createAsyncThunk/store/slices/wishListSlice.js:
--------------------------------------------------------------------------------
1 | // Action Types
2 | const WISHLIST_ADD_ITEM = 'wishList/addItem'
3 | const WISHLIST_REMOVE_ITEM = 'wishList/removeItem'
4 |
5 | // Action Creators
6 | export function addWishListItem(productId) {
7 | return { type: WISHLIST_ADD_ITEM, payload: { productId } }
8 | }
9 | export function removeWishListItem(productId) {
10 | return { type: WISHLIST_REMOVE_ITEM, payload: { productId } }
11 | }
12 |
13 | // Reducer
14 | export default function wishListReducer(state = [], action) {
15 | switch (action.type) {
16 | case WISHLIST_ADD_ITEM:
17 | return [...state, action.payload]
18 |
19 | case WISHLIST_REMOVE_ITEM:
20 | return state.filter(
21 | (wishListItem) => wishListItem.productId !== action.payload.productId
22 | )
23 | default:
24 | return state
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/21_rtk-query/final-code/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/21_rtk-query/final-code/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "tasks": [
3 | {
4 | "id": 1,
5 | "value": "Buy groceries",
6 | "completed": true
7 | },
8 | {
9 | "id": 2,
10 | "value": "Walk the dog",
11 | "completed": true
12 | },
13 | {
14 | "id": 3,
15 | "value": "Finish work project",
16 | "completed": true
17 | },
18 | {
19 | "id": 4,
20 | "value": "Read a book",
21 | "completed": true
22 | },
23 | {
24 | "id": 5,
25 | "value": "Exercise",
26 | "completed": false
27 | },
28 | {
29 | "value": "Complete Redux",
30 | "completed": true,
31 | "id": 6
32 | },
33 | {
34 | "value": "df",
35 | "completed": false,
36 | "id": 7
37 | }
38 | ]
39 | }
--------------------------------------------------------------------------------
/21_rtk-query/final-code/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/21_rtk-query/final-code/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "21_rtk-query",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview",
11 | "json-server": "json-server db.json --watch"
12 | },
13 | "dependencies": {
14 | "@reduxjs/toolkit": "^2.0.1",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0",
17 | "react-redux": "^9.0.4",
18 | "react-router-dom": "^6.21.0"
19 | },
20 | "devDependencies": {
21 | "@types/react": "^18.2.43",
22 | "@types/react-dom": "^18.2.17",
23 | "@vitejs/plugin-react": "^4.2.1",
24 | "autoprefixer": "^10.4.16",
25 | "eslint": "^8.55.0",
26 | "eslint-plugin-react": "^7.33.2",
27 | "eslint-plugin-react-hooks": "^4.6.0",
28 | "eslint-plugin-react-refresh": "^0.4.5",
29 | "json-server": "^0.17.4",
30 | "postcss": "^8.4.32",
31 | "prettier": "^3.1.1",
32 | "prettier-plugin-tailwindcss": "^0.5.9",
33 | "tailwindcss": "^3.3.6",
34 | "vite": "^5.0.8"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/21_rtk-query/final-code/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/21_rtk-query/final-code/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/21_rtk-query/final-code/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from "react-router-dom";
2 |
3 | export default function App() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/21_rtk-query/final-code/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | input[type="checkbox"]:checked + label span:first-of-type {
6 | background-color: #10b981;
7 | border-color: #10b981;
8 | color: #fff;
9 | }
10 |
11 | input[type="checkbox"]:checked + label span:nth-of-type(2) {
12 | text-decoration: line-through;
13 | color: #9ca3af;
14 | }
15 |
16 | .tasks-container {
17 | max-height: calc(100vh - 156px);
18 | }
19 |
20 | .task-app ::-webkit-scrollbar {
21 | width: 8px;
22 | }
23 |
24 | .task-app ::-webkit-scrollbar-track {
25 | background-color: rgb(17 24 39);
26 | -webkit-border-radius: 10px;
27 | border-radius: 10px;
28 | }
29 |
30 | .task-app ::-webkit-scrollbar-thumb {
31 | -webkit-border-radius: 10px;
32 | border-radius: 10px;
33 | background: rgba(85, 74, 208, 0.9);
34 | }
35 |
36 | .task-app ::-webkit-scrollbar-thumb:hover {
37 | background: rgba(85, 74, 208);
38 | }
39 |
--------------------------------------------------------------------------------
/21_rtk-query/final-code/src/main.jsx:
--------------------------------------------------------------------------------
1 | import ReactDOM from "react-dom/client";
2 | import App from "./App.jsx";
3 | import Home from "./Home.jsx";
4 | import { RouterProvider, createBrowserRouter } from "react-router-dom";
5 | // import { ApiProvider } from "@reduxjs/toolkit/query/react";
6 | // import { api } from "./apiSlice.js";
7 |
8 | import { Provider } from "react-redux";
9 | import { store } from "./store.js";
10 |
11 | import "./index.css";
12 |
13 | const router = createBrowserRouter([
14 | {
15 | path: "/",
16 | element: (
17 |
18 |
19 |
20 | ),
21 | children: [
22 | {
23 | path: "/",
24 | element: ,
25 | },
26 | {
27 | path: "/contact",
28 | element: Contact Us
,
29 | },
30 | ],
31 | },
32 | ]);
33 |
34 | ReactDOM.createRoot(document.getElementById("root")).render(
35 | ,
36 | );
37 |
--------------------------------------------------------------------------------
/21_rtk-query/final-code/src/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import { api } from "./apiSlice";
3 |
4 | export const store = configureStore({
5 | reducer: {
6 | [api.reducerPath]: api.reducer,
7 | },
8 | middleware: (getDefaultMiddleware) => [
9 | ...getDefaultMiddleware(),
10 | api.middleware,
11 | ],
12 | });
13 |
--------------------------------------------------------------------------------
/21_rtk-query/final-code/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | }
9 |
--------------------------------------------------------------------------------
/21_rtk-query/final-code/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/21_rtk-query/starter-code/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/21_rtk-query/starter-code/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "tasks": [
3 | {
4 | "id": 1,
5 | "value": "Buy groceries",
6 | "completed": false
7 | },
8 | {
9 | "id": 2,
10 | "value": "Walk the dog",
11 | "completed": true
12 | },
13 | {
14 | "id": 3,
15 | "value": "Finish work project",
16 | "completed": true
17 | },
18 | {
19 | "id": 4,
20 | "value": "Read a book",
21 | "completed": true
22 | },
23 | {
24 | "id": 5,
25 | "value": "Exercise",
26 | "completed": false
27 | },
28 | {
29 | "id": 6,
30 | "value": "Complete Redux",
31 | "completed": false
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/21_rtk-query/starter-code/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/21_rtk-query/starter-code/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "21_rtk-query",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview",
11 | "json-server": "json-server db.json --watch"
12 | },
13 | "dependencies": {
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-router-dom": "^6.21.0"
17 | },
18 | "devDependencies": {
19 | "@types/react": "^18.2.43",
20 | "@types/react-dom": "^18.2.17",
21 | "@vitejs/plugin-react": "^4.2.1",
22 | "autoprefixer": "^10.4.16",
23 | "eslint": "^8.55.0",
24 | "eslint-plugin-react": "^7.33.2",
25 | "eslint-plugin-react-hooks": "^4.6.0",
26 | "eslint-plugin-react-refresh": "^0.4.5",
27 | "json-server": "^0.17.4",
28 | "postcss": "^8.4.32",
29 | "prettier": "^3.1.1",
30 | "prettier-plugin-tailwindcss": "^0.5.9",
31 | "tailwindcss": "^3.3.6",
32 | "vite": "^5.0.8"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/21_rtk-query/starter-code/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/21_rtk-query/starter-code/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/21_rtk-query/starter-code/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from "react-router-dom";
2 |
3 | export default function App() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/21_rtk-query/starter-code/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | input[type="checkbox"]:checked + label span:first-of-type {
6 | background-color: #10b981;
7 | border-color: #10b981;
8 | color: #fff;
9 | }
10 |
11 | input[type="checkbox"]:checked + label span:nth-of-type(2) {
12 | text-decoration: line-through;
13 | color: #9ca3af;
14 | }
15 |
16 | .tasks-container {
17 | max-height: calc(100vh - 156px);
18 | }
19 |
20 | .task-app ::-webkit-scrollbar {
21 | width: 8px;
22 | }
23 |
24 | .task-app ::-webkit-scrollbar-track {
25 | background-color: rgb(17 24 39);
26 | -webkit-border-radius: 10px;
27 | border-radius: 10px;
28 | }
29 |
30 | .task-app ::-webkit-scrollbar-thumb {
31 | -webkit-border-radius: 10px;
32 | border-radius: 10px;
33 | background: rgba(85, 74, 208, 0.9);
34 | }
35 |
36 | .task-app ::-webkit-scrollbar-thumb:hover {
37 | background: rgba(85, 74, 208);
38 | }
39 |
--------------------------------------------------------------------------------
/21_rtk-query/starter-code/src/main.jsx:
--------------------------------------------------------------------------------
1 | import ReactDOM from "react-dom/client";
2 | import App from "./App.jsx";
3 | import Home from "./Home.jsx";
4 | import { RouterProvider, createBrowserRouter } from "react-router-dom";
5 |
6 | import "./index.css";
7 |
8 | const router = createBrowserRouter([
9 | {
10 | path: "/",
11 | element: ,
12 | children: [
13 | {
14 | path: "/",
15 | element: ,
16 | },
17 | {
18 | path: "/contact",
19 | element: Contact Us
,
20 | },
21 | ],
22 | },
23 | ]);
24 |
25 | ReactDOM.createRoot(document.getElementById("root")).render(
26 | ,
27 | );
28 |
--------------------------------------------------------------------------------
/21_rtk-query/starter-code/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | }
9 |
--------------------------------------------------------------------------------
/21_rtk-query/starter-code/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------