My React Learning Note (5)

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

1
2
3
4
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')
}) //fire for any update
//--------------

userEffect(()=>{
console.log('hello')
}, []) //fire for nothing but first time load

//-------------------

userEffect(()=>{
console.log('hello')
}, [user]) //only filre for item in [item]

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
// Then this will be called once when mount!
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:

  1. this.state -> useState

  2. 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;

// only update the one we want!
setUserCredentials({ ...userCredentials, [name]: value });
};

One component will re-render only in three conditions:

  1. Props change
  2. setState from useState
  3. parent re-render

userEffect -> Unmount

1
2
3
4
5
6
7
useEffect(() => {

//clean up function
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));

// cleanup function
return () => {
console.log("I am unsubscribing!");
unsubscribeFromCollection();
};
}, []); // pass empty list to avoid infinite loop

useEffect Cheat Sheet

A quick recap of what we have learned about useEffect:

ComponentDidMount

1
2
3
4
5
6
7
8
9
//Class
componentDidMount() {
console.log('I just mounted!');
}

//Hooks
useEffect(() => {
console.log('I just mounted!');
}, [])

ComponentWillUnmount

1
2
3
4
5
6
7
8
9
//Class
componentWillUnmount() {
console.log('I am unmounting');
}

//Hooks
useEffect(() => {
return () => console.log('I am unmounting');
}, [])

ComponentWillReceiveProps

1
2
3
4
5
6
7
8
9
10
11
//Class
componentWillReceiveProps(nextProps) {
if (nextProps.count !== this.props.count) {
console.log('count changed', nextProps.count);
}
}

//Hooks
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
// src/effects/use-fetch.effect.js


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]);
//only fire when url change, or if we want to always update data whenver something change, don't pass second param
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();

// const func = ()=>{...}
// This will re-assign everytime re-render

//Only when mount
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

  1. Props change
  2. State change
  3. 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

image-20211121155030357

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) => {
/* Input param is the context we get from CollectionsContext.Consumer */
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
//src/providers/cart/cart.provider.jsx
import React, { createContext, useContext, useEffect, useState } from "react";

import { addItemToCart, removeItemFromCart } from "./cart.utils";

// Vehicle to pass around the local state
export const CartContext = createContext({
hidden: true,
toggleHidden: () => {},
cartItems: [],
addItem: () => {},
removeItem: () => {},
clearItemFromCart: () => {},
cartItemsCount: 0,
});

const CartProvider = ({ children }) => {
//the actual state that is passed down to children components
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
//src/index.js

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";

// Now all children components of CartProvider can access the state stored in the CartProvider component through the vehicle of useContext!
ReactDOM.render(
<Provider store={store}>
{/* The actual provider we pass down*/}
<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


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!