This is the 7th day of my participation in the November Gwen Challenge. Check out the details: The last Gwen Challenge 2021

In this book, I will take a closer look at the Hooks in UseRedcer, which are a little difficult to understand and use

Front knowledge

What is Reducer?

Reducer became popular with the emergence of Redux, so we don’t need to know about Redux. Reducer is a function like (state,action) => newState that accepts a state and the action action that changes the state and returns a newState.

For example, 🌰

  function countReducer(state, action) {
        switch(action.type) {
            case 'add':
                return state + 1;
            case 'sub':
                return state - 1;
            default: 
                returnstate; }}Copy the code

The above function is a reducer. Pass in a state and change it by adding and subtracting. Return the changed state

The characteristics of the reducer

Idempotence: the same value, the result is always the same

expect(countReducer(1, { type: 'add' })).equal(2); // expect(countReducer(1, {type: 'add'})).equal(2); / / successCopy the code

The reducer receives the object as state

Note 📢

  • State objects processed by reducer must be immutable, that is, we cannot directly change Object values, because in React, the comparison between new and old states is shallow through object.is () (The Object only compares addresses, and if the Object is directly changed, it points to the same address. So even if the object’s value changes, it is still considered the same value), and the same value does not trigger the change.
  • We can return a new object by deconstructing it
 // Return a newState (newObject)
    function countReducer(state, action) {
        switch(action.type) {
            case 'add':
                return { ...state, count: state.count + 1; }
            case 'sub':
                return { ...state, count: state.count - 1; }
            default: 
                returncount; }}Copy the code

What are the components of an action

Action represents an Action, and its attribute type represents the type of the specific Action, which is the judgment condition of the Reducer execution. Payload The optional attribute payload indicates the data to be carried

Enter the useReducer

UseReducer is an advanced version of useState. When state becomes complicated, useReducer can replace useState to achieve easy maintenance and management of state and better reuse of complex logic

const [state,dispatch] = useReducer(reducder,initState)
Copy the code
  • Parameters: The first is the reducer function as described above, and the second is the initial value of state
  • Return values: The first is the new state returned, and the second is the dispatch method used to call the Reducer function, which accepts an action

Official website small example 🌰

    // First argument: application initialization
    const initialState = {count: 0};

    // Second argument: state's reducer handler
    function reducer(state, action) {
        switch (action.type) {
            case 'increment':
              return {count: state.count + 1};
            case 'decrement':
               return {count: state.count - 1};
            default:
                throw new Error();
        }
    }

    function Counter() {
        Return values: the latest state and dispatch functions
        const [state, dispatch] = useReducer(reducer, initialState);
        return (
            <>// The useReducer will return the final state according to the dispatch action and rerender Count will be triggered: {state.count} // Dispatch receives a reducer action parameter, which triggers the reducer function and updates the reducer status<button onClick={()= > dispatch({type: 'increment'})}>+</button>
                <button onClick={()= > dispatch({type: 'decrement'})}>-</button>
            </>
        );
    }
Copy the code

Examples from the renovation project 🌰

    function LoginPage() {
        const [name, setName] = useState(' '); / / user name
        const [pwd, setPwd] = useState(' '); / / password
        const [isLoading, setIsLoading] = useState(false); // Whether to display loading, sending a request
        const [error, setError] = useState(' '); // Error message
        const [isLoggedIn, setIsLoggedIn] = useState(false); // Whether to log in

        const login = (event) = > {
            event.preventDefault();
            setError(' ');
            setIsLoading(true);
            login({ name, pwd })
                .then(() = > {
                    setIsLoggedIn(true);
                    setIsLoading(false);
                })
                .catch((error) = > {
                    // Login failure: Error information is displayed, the user name and password are cleared, and the loading flag is cleared
                    setError(error.message);
                    setName(' ');
                    setPwd(' ');
                    setIsLoading(false);
                });
        }
        return ( 
            // Return to page JSX Element)}Copy the code

Problem: A series of complex states, scattered and difficult to maintain

After overwriting using useReducer

    const initState = {
        name: ' '.pwd: ' '.isLoading: false.error: ' '.isLoggedIn: false,}function loginReducer(state, action) {
        switch(action.type) {
            case 'login':
                return {
                    ...state,
                    isLoading: true.error: ' ',}case 'success':
                return {
                    ...state,
                    isLoggedIn: true.isLoading: false,}case 'error':
                return {
                    ...state,
                    error: action.payload.error,
                    name: ' '.pwd: ' '.isLoading: false,}default: 
                returnstate; }}function LoginPage() {
        const [state, dispatch] = useReducer(loginReducer, initState);
        const { name, pwd, isLoading, error, isLoggedIn } = state;
        const login = (event) = > {
            event.preventDefault();
            dispatch({ type: 'login' });
            login({ name, pwd })
                .then(() = > {
                    dispatch({ type: 'success' });
                })
                .catch((error) = > {
                    dispatch({
                        type: 'error'
                        payload: { error: error.message }
                    });
                });
        }
        return ( 
            // Return to page JSX Element)}Copy the code

Improved: although the amount of code has become more, but the overall logic is clearer, logic reuse can be realized, unified management state is easy to maintain.

Reducer implements how, and the component only controls what. If login is a component, call Dispatch ({type: ‘login’}).

UseReducer +useContext Login example 🌰

    // Define the initialization value
    const initState = {
        name: ' '.pwd: ' '.isLoading: false.error: ' '.isLoggedIn: false,}// Define the state[business] process logic reducer function
    function loginReducer(state, action) {
        switch(action.type) {
            case 'login':
                return {
                    ...state,
                    isLoading: true.error: ' ',}case 'success':
                return {
                    ...state,
                    isLoggedIn: true.isLoading: false,}case 'error':
                return {
                    ...state,
                    error: action.payload.error,
                    name: ' '.pwd: ' '.isLoading: false,}default: 
                returnstate; }}// Define context
    const LoginContext = React.createContext();
    function LoginPage() {
        const [state, dispatch] = useReducer(loginReducer, initState);
        const { name, pwd, isLoading, error, isLoggedIn } = state;
        const login = (event) = > {
            event.preventDefault();
            dispatch({ type: 'login' });
            login({ name, pwd })
                .then(() = > {
                    dispatch({ type: 'success' });
                })
                .catch((error) = > {
                    dispatch({
                        type: 'error'
                        payload: { error: error.message }
                    });
                });
        }
        // Use context to share dispatches
        return ( 
            <LoginContext.Provider value={dispatch}><... ><LoginButton />
            </LoginContext.Provider>State const dispatch = useContext(LoginContext); Const click = () => {if (error) {// Action dispatch({type: 'error' payload: {error: error.message } }); }}}Copy the code

conclusion

Advantages: Use useReducer instead of complex state. If the component hierarchy is deep and requires a child component to trigger state, you can also use useContext to pass the dispatch