Notes on Aug 15th: lots of stuff I haven’t understanded, just try to learn some whole framework
Redux Overview
Redux is a predictable state container for JavaScript apps.
It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test.
Install Redux DevTools
in chrome web store
1
frontend % npm i redux react-redux redux-thunk redux-devtools-extension
React-Redux is the official React UI bindings layer for Redux. It lets your React components read data from a Redux store, and dispatch actions to the store to update state.
Redux Thunk middleware allows you to write action creators
that return a function
instead of an action
. The thunk can be used to delay the dispatch
of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.
With a plain basic Redux store, you can only do simple synchronous updates by dispatching an action. Middleware extends the store’s abilities, and lets you write async logic
that interacts with the store.
Product List Reducer & Action
Create a src/constants/productConstant.js
to store the Strings
1
2
3
export const PRODUCT_LIST_REQUEST = "PRODUCT_LIST_REQUEST" ;
export const PRODUCT_LIST_SUCCESS = "PRODUCT_LIST_SUCCESS" ;
export const PRODUCT_LIST_FAIL = "PRODUCT_LIST_FAIL" ;
Create src/reducers/productReducers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import {
PRODUCT_LIST_REQUEST ,
PRODUCT_LIST_SUCCESS ,
PRODUCT_LIST_FAIL ,
} from "../constants/productConstants" ;
export const productListReducer = ( state = { products : [] }, action ) => {
switch ( action . type ) {
case PRODUCT_LIST_REQUEST :
return { loading : true , products : [] };
case PRODUCT_LIST_SUCCESS :
return { loading : false , productions : action . payload };
case PRODUCT_LIST_FAIL :
return { loading : false , error : action . payload };
default :
return state ;
}
};
Create src/actions/productActions.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import axios from "axios" ;
import {
PRODUCT_LIST_REQUEST ,
PRODUCT_LIST_SUCCESS ,
PRODUCT_LIST_FAIL ,
} from "../constants/productConstants" ;
export const listProducts = () => async ( dispatch ) => {
try {
dispatch ({ type : PRODUCT_LIST_REQUEST });
const { data } = await axios . get ( "/api/products" );
dispatch ({
type : PRODUCT_LIST_SUCCESS ,
payload : data ,
});
} catch ( error ) {
dispatch ({
type : PRODUCT_LIST_FAIL ,
payload :
error . response && error . response . data . message
? error . response . data . message
: error . message ,
});
}
};
Create a Redux Store
Create frontend/src/store.js
createStore(reducer, [preloadedState], [enhancer])
: Creates a Redux store that holds the complete state tree of your app. There should only be a single store in your app.
reducer (Function)
: A reducing function
that returns the next state tree
, given the current state tree and an action to handle.
A reducer
is a function that accepts an accumulation and a value and returns a new accumulation. They are used to reduce a collection of values down to a single value .
[preloadedState] (any)
: The initial state
. You may optionally specify it to hydrate the state from the server in universal apps, or to restore a previously serialized user session.
If you produced reducer with combineReducers
, this must be a plain object
with the same shape as the keys passed to it. Otherwise, you are free to pass anything that your reducer can understand.
[enhancer] (Function)
: The store enhancer. You may optionally specify it to enhance the store with third-party capabilities such as middleware, time travel, persistence, etc.
The only store enhancer that ships with Redux is applyMiddleware()
.
combineReducers
: turns an object whose values are different reducing functions into a single reducing function you can pass to createStore
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { createStore , combineReducers , applyMiddleware } from "redux" ;
import thunk from "redux-thunk" ;
import { composeWithDevTools } from "redux-devtools-extension" ;
const reducer = combineReducers ({
productList : productListReducer ,
});
const initialState = {};
const middleware = [ thunk ];
const store = createStore (
reducer ,
initialState ,
composeWithDevTools ( applyMiddleware (... middleware ))
);
export default store ;
In index.js
The <Provider>
component makes the Redux store available to any nested components that need to access the Redux store.
most applications will render a <Provider>
at the top level, with the entire app’s component tree inside of it.
to replace <React.StrictMode>
1
2
3
4
5
6
7
8
9
import ReactDOM from "react-dom" ;
import { Provider } from "react-redux" ;
import store from "./store" ;
ReactDOM . render (
< Provider store = { store } >
< App />
< /Provider>,
document . getElementById ( "root" )
);
Message and Loader Components
Create frontend/src/components/Message.js
1
2
3
4
5
6
7
8
9
10
11
12
import React from "react" ;
import { Alert } from "react-bootstrap" ;
const Message = ({ variant , children }) => {
return < Alert variant = { variant } > { children } < /Alert>;
};
Message . defaultProps = {
variant : "info" ,
};
export default Message ;
Create frontend/src/components/Loader.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from "react" ;
import { Spinner } from "react-bootstrap" ;
const Loader = () => {
return (
< Spinner
animation = "border"
role = "status"
style = {{
width : "100px" ,
height : "100px" ,
margin : "auto" ,
display : "block" ,
}}
>
< span className = "sr-only" > Loading ... < /span>
< /Spinner>
);
};
export default Loader ;
Bring Redux State into HomeScreen
In HomeScreen.js
import useDispatch
and useSelector
no need axios
and useState()
clear everything in useEffect()
add message
& loader
components
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// import axios from "axios";
import React , { useEffect } from "react" ;
import { useDispatch , useSelector } from "react-redux" ;
import { Row , Col } from "react-bootstrap" ;
import Product from "../components/Product" ;
import Message from "../components/Message" ;
import Loader from "../components/Loader" ;
import { listProducts } from "../actions/productActions" ;
const HomeScreen = () => {
//const [products, setProducts] = useState([]);
const dispatch = useDispatch ();
const productList = useSelector (( state ) => state . productList );
const { loading , error , products } = productList ;
useEffect (() => {
dispatch ( listProducts ());
}, [ dispatch ]);
return (
<>
< h1 > Latest Products < /h1>
{ loading ? (
< Loader />
) : error ? (
< Message variant = "danger" > { error } < /Message>
) : (
< Row >
{ products . map (( product ) => (
< Col key = { product . _ id } sm = { 12 } md = { 6 } lg = { 4 } xl = { 3 } >
< Product product = { product } />
< /Col>
))}
< /Row>
)}
< />
);
};
export default HomeScreen ;
Product Details Reducer & Action
In productConstants.js
1
2
3
export const PRODUCT_DETAILS_REQUEST = "PRODUCT_DETAILS_REQUEST" ;
export const PRODUCT_DETAILS_SUCCESS = "PRODUCT_DETAILS_SUCCESS" ;
export const PRODUCT_DETAILS_FAIL = "PRODUCT_DETAILS_FAIL" ;
In src/reducers/productReducers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {
PRODUCT_DETAILS_REQUEST ,
PRODUCT_DETAILS_SUCCESS ,
PRODUCT_DETAILS_FAIL ,
} from "../constants/productConstants" ;
export const productDetailsReducer = (
state = { product : { reviews : [] } },
action
) => {
switch ( action . type ) {
case PRODUCT_DETAILS_REQUEST :
return { loading : true , ... state };
case PRODUCT_DETAILS_SUCCESS :
return { loading : false , product : action . payload };
case PRODUCT_DETAILS_FAIL :
return { loading : false , error : action . payload };
default :
return state ;
}
};
In src/actions/productActions.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import {
PRODUCT_DETAILS_REQUEST ,
PRODUCT_DETAILS_SUCCESS ,
PRODUCT_DETAILS_FAIL ,
} from "../constants/productConstants" ;
export const listProductDetails = ( id ) => async ( dispatch ) => {
try {
dispatch ({ type : PRODUCT_DETAILS_REQUEST });
const { data } = await axios . get ( `/api/products/ ${ id } ` );
dispatch ({
type : PRODUCT_DETAILS_SUCCESS ,
payload : data ,
});
} catch ( error ) {
dispatch ({
type : PRODUCT_DETAILS_FAIL ,
payload :
error . response && error . response . data . message
? error . response . data . message
: error . message ,
});
}
};
In store.js
1
2
3
4
5
6
7
8
9
import {
productListReducer ,
productDetailsReducer ,
} from "./reducers/productReducers" ;
const reducer = combineReducers ({
productList : productListReducer ,
productDetails : productDetailsReducer ,
});
In ProductScreen.js
no need axios
and useState()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
//const [product, setProduct] = useState({});
import React , { useEffect } from "react" ;
import { Link } from "react-router-dom" ;
import { useDispatch , useSelector } from "react-redux" ;
import { Row , Col , Image , ListGroup , Card , Button } from "react-bootstrap" ;
import Rating from "../components/Rating" ;
import { listProductDetails } from "../actions/productActions" ;
import Loader from "../components/Loader" ;
import Message from "../components/Message" ;
const ProductScreen = ({ match }) => {
const dispatch = useDispatch ();
const productDetails = useSelector (( state ) => state . productDetails );
const { loading , error , product } = productDetails ;
useEffect (() => {
dispatch ( listProductDetails ( match . params . id ));
}, [ dispatch , match ]);
return (
<>
< Link className = "btn btn-light my-3" to = "/" >
Go Back
< /Link>
{ loading ? (
< Loader />
) : error ? (
< Message variant = "danger" > { error } < /Message>
) : (
< Row >
< Col md = { 6 } >
< Image src = { product . image } alt = { product . name } fluid />
< /Col>
< Col md = { 3 } >
< ListGroup variant = "flush" >
< ListGroup . Item >
< h3 > { product . name } < /h3>
< /ListGroup.Item>
< ListGroup . Item >
< Rating
value = { product . rating }
text = { ` ${ product . numReviews } reviews` }
/>
< /ListGroup.Item>
< ListGroup . Item > Price : $ { product . price } < /ListGroup.Item>
< ListGroup . Item >
Description : { product . description }
< /ListGroup.Item>
< /ListGroup>
< /Col>
< Col md = { 3 } >
< Card >
< ListGroup variant = "flush" >
< ListGroup . Item >
< Row >
< Col > Price :< /Col>
< Col >
< strong > $ { product . price } < /strong>
< /Col>
< /Row>
< /ListGroup.Item>
< ListGroup . Item >
< Row >
< Col > Status :< /Col>
< Col >
{ product . countInStock > 0 ? "In Stock" : "Out of Stock" }
< /Col>
< /Row>
< /ListGroup.Item>
< ListGroup . Item >
< Button
className = "btn-block"
type = "button"
disabled = { product . countInStock === 0 }
>
Add To Cart
< /Button>
< /ListGroup.Item>
< /ListGroup>
< /Card>
< /Col>
< /Row>
)}
< />
);
};
export default ProductScreen ;
Resources and tutorial for Redux: