This article will introduce how to use the React Hook API to build the React project. Instead of traditional Redux, it will use useReducer and useContext to implement state distribution management. Finally, it will explain how to make asynchronous data requests in the React Hook project. Hook is a new feature in React 16.8. It lets you use state and other React features without having to write a class. If you’re not familiar with React Hook, check out the introduction of the new feature on React’s website.

Provier components

Those familiar with Redux should know the Provier component in React-Redux, which uses the Provider component to distribute the state of a written store to any of its subordinate components. Provier uses the React Context property, so we also use the React Hook useContext property to implement state distribution.

useContext

const value = useContext(myContext)
Copy the code

UseContext receives a context object (the return value of react. createContext) and returns the current value of the context. The current context value is determined by the < MyContext.provider > value prop of the upper-layer component closest to the current component.

When the most recent <MyContext.Provider> update is made to the component’s upper layer, the Hook triggers a rerender and uses the latest context value passed to MyContext Provider.


In addition to this, you also need to work with useReducer,

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);
Copy the code

UseReducer is an alternative to useState. It receives a Reducer of the form (state, action) => newState and returns the current state and its accompanying dispatch method. (If you’re familiar with Redux, you already know how it works.)


With these two apis, we can start writing the Provider component. First, create the context directory and create basic reducer and index files. MainReducer. Js implementation code is as follows:

import mainConstants from './mainConstants';

export const mainInitialState = {
    error: ' '.res: [].url: '/pool/query'.loading: false};export default (state, action) => {
    switch (action.type) {
        case mainConstants.INIT_PAGE:
            return{... state,res: action.payload};

        case mainConstants.TO_SEARCH:
            return{... state,url: action.payload};

        case mainConstants.PAGE_LOADING:
            return{... state,loading: action.payload};

        case mainConstants.CHANGE_ERROR:
            return{... state,error: action.payload};

        default:
            returnstate; }};Copy the code

Index.js implementation code is as follows:

import React, { useReducer, createContext } from 'react';
import mainReducer, {mainInitialState} from './main/mainReducer';

const context = createContext({});
const reducer = {
    main: mainReducer
};

// Add the status change log
function middlewareLog(lastState, action) {
    const type = action.type.split('_') [0].toLowerCase();
    const nextState = reducer[type](lastState, action);
    console.log(
        `%c|------- redux: ${action.type}-- -- -- -- -- - | `.`background: rgb(70, 70, 70); color: rgb(240, 235, 200); width:100%; `,);console.log('|--last:', lastState);
    console.log('|--next:', nextState);
    return nextState;
}

const Provider = props= > {
    const [mainState, mainDispatch] = useReducer(middlewareLog, mainInitialState);
    const combined = {
        main: {
            state: mainState,
            dispatch: mainDispatch,
        },
    };
    return (<context.Provider value={combined}>
        {props.children}
    </context.Provider>)}; export {context}; export default Provider;Copy the code

Then, in the main entrance of the project, Provier component is added to achieve state distribution management.

import React from 'react';
import ReactDOM from 'react-dom';
import Provider from './context/';
import App from './App';

ReactDOM.render(<Provider>
    <App />
</Provider>.document.getElementById('root'));
Copy the code

Now that state distribution is created, it’s time to see how to get and use state in the component. The app.js code is as follows:

import React, {useContext} from 'react';
import {context} from "./context";

function App() {
    const {state, dispatch} = useContext(context).main;
    return (
        <div>
            <div>
                hello
            </div>
        </div>
    );
}

export default App;
Copy the code

Next we set up a business scenario where 1) the page initially loads the data and 2) the data can be reloaded based on the request parameters. According to the three principles of Redux, create an action file with the following code:

import mainConstants from './mainConstants';

export const initPage = value= > ({type: mainConstants.INIT_PAGE, payload: value});

export const toSearch = value= > ({type: mainConstants.TO_SEARCH, payload: value});

export const pageLoading = value= > ({type: mainConstants.PAGE_LOADING, payload: value});

export const changeError = value= > ({type: mainConstants.CHANGE_ERROR, payload: value});
Copy the code

MainConstants code is as follows:

const create = str= > 'MAIN_' + str;

export default {
    INIT_PAGE: create('INIT_PAGE'),
    TO_SEARCH: create('TO_SEARCH'),
    PAGE_LOADING: create('PAGE_LOADING'),
    CHANGE_ERROR: create('CHANGE_ERROR'),}Copy the code

Think about the business scenario, how do you want to fetch data during page rendering? How do you pass the parameters provided by the search box to the interface? Here, useEffect is mainly used.

useEffect

useEffect(didUpdate);
Copy the code

UseEffect lets you perform side effects in function components. Manipulating data fetching, setting up subscriptions, and manually changing the DOM in the React component are all side effects.

If you’re familiar with React class lifecycle functions, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount.

It takes two arguments. The first argument is an execution function, and how and when the execution function is handled depends on the second argument. Generally, if you want to execute effect only once (only when the component is mounted and unloaded), you can pass an empty array ([]), Similar to componentDidMount in the life cycle. If you want to execute according to the state value, you simply pass the state value into the array. This API has many specifications to explore, you can refer to the FAQ of the official documentation.


Then rewrite app.js as follows:

import React, {useContext, useEffect} from 'react';
import {context} from "./context";
import * as mainAction from "./context/main/mainAction";
import {getJson} from "./util";

function App() {
    const {state, dispatch} = useContext(context).main;

    // Design the internal variable ignore and change the state when ignore is True,
    // Finally returns an execution action that prevents the state from changing when the component is uninstalled
    useEffect((a)= > {
        let ignore = false;
        const getData = async() = > {try {
                dispatch(mainAction.pageLoading(true));
                const res = await getJson(state.url);
                if(! ignore) { dispatch(mainAction.initPage(res)); } dispatch(mainAction.pageLoading(false));
            } catch (err) {
                if(! ignore) { dispatch(mainAction.changeError(err.message)); }}}; getData();return (a)= > {
            ignore = true
        };
    }, [state.url, dispatch]);  // Execute only when the URL changes

    return (
        <div>
            <div>
                <button onClick={()= >dispatch(mainAction.toSearch(state.url + '? bagName=224-truck2_2019-05-09-14-28-19_41-0'))}>search</button>
                {state.res.map(item =>
                    <p key={item.id}>{item.bagName}</p>
                )}
            </div>
        </div>
    );
}

export default App;
Copy the code

Here is a request function wrapped in AXIos, with the following code:

import axios from 'axios';

const instance = getDefaultInstance();

export function getJson(url, data) {
    return instance.get(url, { params: data });
}

function getDefaultInstance() {
    const instance = axios.create({
        baseURL: '/'.withCredentials: true
    });
    instance.interceptors.response.use(res= > {
        return res.data.data;
    }, err => {
        throw err;
    });
    return instance;
}
Copy the code

The official documentation states that it is desirable that we place the function for asynchronous requests directly in useEffect, not in the component.

This completes the original business scenario, but there is one more refinement that can be made. As usual work, almost every page will have the function of initial request data and query data, so we can customize the Hook to encapsulate the parts of the same logic.

Customize the Hook

When we want to share logic between two functions, we extract it into a third function. Components and hooks are functions, so the same applies.

A custom Hook is a function whose name starts with “use” and officially must start with “use”. Other hooks can be called from within the function

The idea is that the custom hook manages the state itself, so useState is used here,


useState

const [state, setState] = useState(initialState);
Copy the code

UseState is a function that returns a state and updates the state.

During initial rendering, the state returned is the same as the value of the first parameter passed in.

The setState function is used to update state. It receives a new state value and queues a re-rendering of the component.

In subsequent rerenders, the first value returned by useState will always be the latest state after the update.


Rewrite app.js as follows:

import React, {useContext, useEffect, useState} from 'react';
import {context} from "./context";
import * as mainAction from "./context/main/mainAction";
import {getJson} from "./util";

function useInitPage({state, dispatch, action}) {
    const [res, setRes] = useState(state.res);
    const [url, setUrl] = useState(state.url);

    const addValue = url= > setUrl(url);

    useEffect((a)= > {
        let ignore = false;
        const getData = async() = > {try {
                dispatch(action.pageLoading(true));
                const res = await getJson(url);
                if(! ignore) { setRes(res);// It is also possible not to return res
                    dispatch(action.initPage(res));
                }
                dispatch(action.pageLoading(false));
            } catch (err) {
                if(! ignore) { dispatch(action.changeError(err.message)); }}}; getData();return (a)= > { ignore = true };
    }, [url, dispatch]);

    return {res, addValue};
}

function App() {
    const {state, dispatch} = useContext(context).main;
    const {res, addValue} = useInitPage({state, dispatch, action: mainAction});
    
    useEffect((a)= > {
        if(state.error ! = =' ') {
            alert(state.error);
        }
    }, [state.error]);

    return (
        <div>
            <div>
                <button onClick={()= >addValue(state.url + '? bagName=224-truck2_2019-05-09-14-28-19_41-0')}>search</button>
                {res.map(item =>
                    <p key={item.id}>{item.bagName}</p>
                )}
            </div>
        </div>
    );
}

export default App;
Copy the code

conclusion

This article mainly realizes the basic part of the business scenario through several new apis of React Hooks. The implementation code is a little simple, and the concept introduction is also a little simple, but it is very practical from the start. There are some issues with the Class component that make it a bit harder to get started, which is why React Hook is officially recommended. There are no plans to remove classes, but the concurrent class and Hook strategy is recommended.

Attach the github source address