Hooks
My React Learning Note (5)
Topic: Hooks
useSelector, useDispatch
useSelector
, useDispatch
是两个 react-redux
提供的hooks,这样就不需要 mapStateToProps
和 mapDispatchToProps
useState
https://github.com/ZhangMYihua/use-state-example
const [name, setName] = useState('Haha' ) <button onClick = {() => setName("Wahaha" )}>Set Name to Wahaha </button>
useEffect
a function gets called whenever the update lifecycle method (re-render) is fired
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 userEffect(()=> { console .log('hello' ) }) userEffect(()=> { console .log('hello' ) }, []) userEffect(()=> { console .log('hello' ) }, [user])
use Async function in useEffect
1 2 3 4 5 6 7 8 useEffect(()=> { const fetchFunc = async () =>{ const response = await fetch(`https://.....` ) const resJson = await response.json() setUser(resJson[0 ]) } fetchFunc(); })
The code above will cause an infinite loop between:
setUser (update DOM) -> useEffect -> setUser -> useEffect…
1 2 3 4 5 6 7 8 9 useEffect(()=> { const fetchFunc = async () =>{ const response = await fetch(`https://.....` ) const resJson = await response.json() setUser(resJson[0 ]) } fetchFunc(); }, [searchQuery])
If we want condition, we put them into useEffect
Class-based component to Function-based component
Some thing we changed from class-based components to function-based components:
this.state
-> useState
this.props
We destructure props we want from the input params of the function component
1 2 3 4 5 6 7 8 9 const SignIn = ({ emailSignInStart, googleSignInStart } ) => { const [userCredentials, setUserCredentials] = useState({ email: "" , password: "" , }); return (...) }
注意,如果当我们把多个 state values 变成一个 object 然后一起更新时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const [userCredentials, setUserCredentials] = useState({ displayName: "" , email: "" , password: "" , confirmPassword: "" , });const { displayName, email, password, confirmPassword } = userCredentials; const handleChange = (event ) => { console .log("here" ); console .log(event.target); const { name, value } = event.target; setUserCredentials({ ...userCredentials, [name]: value }); };
One component will re-render only in three conditions:
Props change
setState
from useState
parent re-render
userEffect -> Unmount
1 2 3 4 5 6 7 useEffect(() => { return () => { } })
1 2 3 4 5 6 7 8 9 10 11 12 13 useEffect(() => { console .log("I am subscribing" ); const unsubscribeFromCollection = firestore .collection("collections" ) .onSnapshot((snapshot ) => console .log(snapshot)); return () => { console .log("I am unsubscribing!" ); unsubscribeFromCollection(); }; }, []);
useEffect Cheat Sheet
A quick recap of what we have learned about useEffect:
ComponentDidMount
1 2 3 4 5 6 7 8 9 componentDidMount ( ) { console .log('I just mounted!' ); } useEffect(() => { console .log('I just mounted!' ); }, [])
ComponentWillUnmount
1 2 3 4 5 6 7 8 9 componentWillUnmount ( ) { console .log('I am unmounting' ); } useEffect(() => { return () => console .log('I am unmounting' ); }, [])
ComponentWillReceiveProps
1 2 3 4 5 6 7 8 9 10 11 componentWillReceiveProps (nextProps ) { if (nextProps.count !== this .props.count) { console .log('count changed' , nextProps.count); } } useEffect(() => { console .log('count changed' , props.count); }, [props.count])
Custom Hook
https://github.com/ZhangMYihua/custom-hook-example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { useState, useEffect } from 'react' ;const useFetch = url => { const [data, setData] = useState(null ); useEffect(() => { const fetchData = async () => { const res = await fetch(url); const dataArray = await res.json(); setData(dataArray[0 ]); }; fetchData(); }, [url]); return data; };export default useFetch;
useReducer
https://github.com/ZhangMYihua/useReducer-example
https://reactjs.org/docs/hooks-reference.html#usereducer
https://react-redux.js.org/api/hooks
React-Redux Hook
useSelector, useDispatch
useSelector: Allows you to extract data from the Redux store state, using a selector function.
The selector is approximately equivalent to the mapStateToProps
argument to connect
conceptually.
useSelector()
will also subscribe to the Redux store, and run your selector whenever an action is dispatched.
useDispatch: This hook returns a reference to the dispatch
function from the Redux store. You may use it to dispatch actions as needed.
selector itself is a function receives state object
useSelector
receives a selector that returns some state, and useSelector will ensure to up-to-date with the state.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const App = () => { const currentUser = useSelector(selectCurrentUser); const dispatch = useDispatch(); useEffect(() => { dispatch(checkUserSession()); }, [dispatch]); return (...) }
Adavanced useState, useEffect
When a functional component gets re-rendered, do it from top to bottom in synchronous manner.
A component is re-rendered if
Props change
State change
parent re-render
We can pass in a function into useState
1 setCount((prev ) => prev+1 )
If a function is dependent on a state, we shall put that function into useEffect
. If not, we can place it out of component so it only renders once.
useCallback
If a function depends on a state but is outside of useEffect
We cannot know its dependency from looking at the code.
we can use useEffect
to preserve function outside of useEffect
and know its dependency
1 2 3 4 5 6 7 const myFunc = useCallback(()=> { console .log('effect on' + test1) }, [test1]) useEffect(()=> { myFunc() }, myFunc)
useMemo
1 2 3 4 5 6 7 cosnt myObj = { a: 'my value of a is ' + test1 } useEffect(()=> { console .log(myObj.a) }, [myObj])
This will always be re-rendered every single time regardless of whether myObj
changed or not
(reference comparison, a new object is initialize every single time for the functional component re-rendering)
1 2 3 4 5 6 7 cosnt myObj = useMemo(() => ({ a: 'my value of a is ' + test1 }), [test1]) useEffect(()=> { console .log(myObj.a) }, [myObj])
useMemo and useCallback can be very helpful when you want some behavior to be performed based on a specific object or function
useLayoutEffect
useEffect
runs after the paint of the component
useLayoutEffect
runs before the paint of the component
useContext
Component way of using context (replacing Redux)
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 import React from "react" ;import { connect } from "react-redux" ;import CollectionItem from "../../components/collection-item/collection-item.component" ;import { selectCollection } from "../../redux/shop/shop.selectors" ;import "./collection.styles.scss" ;import CollectionsContext from "../../contexts/collections/collections.context" ;const CollectionPage = ({ match } ) => { return ( <CollectionsContext.Consumer> {(collections ) => { const collection = collections[match.params.collectionId]; const { title, items } = collection; return ( <div className="collection-page" > <h2 className="title" >{title}</h2> <div className="items" > {items.map((item ) => ( <CollectionItem key={item.id} item={item} /> ))} </div> </div> ); }} </CollectionsContext.Consumer> ); };const mapStateToProps = (state, ownProps ) => ({ collection: selectCollection(ownProps.match.params.collectionId)(state), });export default CollectionPage;
useContext
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 React, { useContext } from "react" ;import { connect } from "react-redux" ;import CollectionItem from "../../components/collection-item/collection-item.component" ;import { selectCollection } from "../../redux/shop/shop.selectors" ;import "./collection.styles.scss" ;import CollectionsContext from "../../contexts/collections/collections.context" ;const CollectionPage = ({ match } ) => { const collections = useContext(CollectionsContext); const { title, items } = collections[match.params.collectionId]; return ( <div className="collection-page" > <h2 className="title" >{title}</h2> <div className="items" > {items.map((item ) => ( <CollectionItem key={item.id} item={item} /> ))} </div> </div> ); };export default CollectionPage;
However, useContext
is not very handy when we need to update a state. The problem it solves is to easily pass a state of a high node in the DOM tree easily to the lower state.
An example:
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 import React, { createContext, useContext, useEffect, useState } from "react" ;import { addItemToCart, removeItemFromCart } from "./cart.utils" ;export const CartContext = createContext({ hidden: true , toggleHidden: () => {}, cartItems: [], addItem: () => {}, removeItem: () => {}, clearItemFromCart: () => {}, cartItemsCount: 0 , });const CartProvider = ({ children } ) => { const [cartItems, setCartItems] = useState([]); const [cartItemsCount, setCartItemsCount] = useState(0 ); const [hidden, setHidden] = useState(true ); const toggleHidden = () => setHidden(!hidden); const addItem = (item ) => setCartItems(addItemToCart(cartItems, item)); return ( <CartContext.Provider value={{ hidden, toggleHidden, cartItems, addItem, cartItemsCount }} > {children} </CartContext.Provider> ); };export default CartProvider;
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 React from "react" ;import ReactDOM from "react-dom" ;import { BrowserRouter } from "react-router-dom" ;import { Provider } from "react-redux" ;import { PersistGate } from "redux-persist/integration/react" ;import { store, persistor } from "./redux/store" ;import CartProvider from "./providers/cart/cart.provider" ;import "./index.css" ;import App from "./App" ; ReactDOM.render( <Provider store={store}> {} <CartProvider> <BrowserRouter> <PersistGate persistor={persistor}> <App /> </PersistGate> </BrowserRouter> </CartProvider> </Provider>, document .getElementById("root" ) );
具体实现看下面的代码, 非常有帮助!
Very helpful!
Code below:
https://github.com/ZhangMYihua/react-context-practice
https://github.com/ZhangMYihua/react-context-complete