├── 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 | {title} 6 |
7 |
8 |

9 | {title} 10 |

11 |
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 | {title} 6 |
7 |
8 |

9 | {title} 10 |

11 |
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 | {title} 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 |
11 |
12 |

13 | Shopee 14 |

15 | 16 | cart-icon 17 |
18 | {cartItems.reduce( 19 | (accumulator, currentItem) => accumulator + currentItem.quantity, 20 | 0 21 | )} 22 |
23 | 24 |
25 |
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 | {title} 10 |
11 |
12 |

13 | {title} 14 |

15 |
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 | {title} 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 | cart-icon 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 | {title} 6 |
7 |
8 |

9 | {title} 10 |

11 |
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 | {title} 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 |
10 |
11 |

12 | Shopee 13 |

14 | 15 | cart-icon 16 |
17 | {cartItems.reduce( 18 | (accumulator, currentItem) => accumulator + currentItem.quantity, 19 | 0 20 | )} 21 |
22 | 23 |
24 |
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 | {title} 10 |
11 |
12 |

13 | {title} 14 |

15 |
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 | {title} 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 |
10 |
11 |

12 | Shopee 13 |

14 | 15 | cart-icon 16 |
17 | {cartItems.reduce( 18 | (accumulator, currentItem) => accumulator + currentItem.quantity, 19 | 0 20 | )} 21 |
22 | 23 |
24 |
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 | {title} 10 |
11 |
12 |

13 | {title} 14 |

15 |
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 | {title} 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 |
10 |
11 |

12 | Shopee 13 |

14 | 15 | cart-icon 16 |
17 | {cartItems.reduce( 18 | (accumulator, currentItem) => accumulator + currentItem.quantity, 19 | 0 20 | )} 21 |
22 | 23 |
24 |
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 | {title} 10 |
11 |
12 |

13 | {title} 14 |

15 |
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 | {title} 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 |
10 |
11 |

12 | Shopee 13 |

14 | 15 | cart-icon 16 |
17 | {cartItems.reduce( 18 | (accumulator, currentItem) => accumulator + currentItem.quantity, 19 | 0 20 | )} 21 |
22 | 23 |
24 |
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 | {title} 10 |
11 |
12 |

13 | {title} 14 |

15 |
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 | {title} 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 |
10 |
11 |

12 | Shopee 13 |

14 | 15 | cart-icon 16 |
17 | {cartItems.reduce( 18 | (accumulator, currentItem) => accumulator + currentItem.quantity, 19 | 0 20 | )} 21 |
22 | 23 |
24 |
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 | {title} 10 |
11 |
12 |

13 | {title} 14 |

15 |
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 | {title} 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 |
10 |
11 |

12 | Shopee 13 |

14 | 15 | cart-icon 16 |
17 | {cartItems.reduce( 18 | (accumulator, currentItem) => accumulator + currentItem.quantity, 19 | 0 20 | )} 21 |
22 | 23 |
24 |
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 | {title} 10 |
11 |
12 |

13 | {title} 14 |

15 |
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 | {title} 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 | {title} 10 |
11 |
12 |

13 | {title} 14 |

15 |
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 | {title} 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 | {title} 10 |
11 |
12 |

13 | {title} 14 |

15 |
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 | {title} 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 | {title} 10 |
11 |
12 |

13 | {title} 14 |

15 |
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 | {title} 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 |
21 |
22 |

23 | Shopee 24 |

25 | 26 | cart-icon 27 |
28 | {cartItems.reduce( 29 | (accumulator, currentItem) => accumulator + currentItem.quantity, 30 | 0 31 | )} 32 |
33 | 34 |
35 |
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 | {title} 10 |
11 |
12 |

13 | {title} 14 |

15 |
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 | {title} 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 |
21 |
22 |

23 | Shopee 24 |

25 | 26 | cart-icon 27 |
28 | {cartItems.reduce( 29 | (accumulator, currentItem) => accumulator + currentItem.quantity, 30 | 0 31 | )} 32 |
33 | 34 |
35 |
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 | {title} 10 |
11 |
12 |

13 | {title} 14 |

15 |
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 | --------------------------------------------------------------------------------