With the new API for hooks added in React v16.8.0, it’s important to understand how to use it and be able to write a few custom hooks for our business.

1. One of the commonly used hooks

There are several built-in hooks officially provided, let’s have a brief look at their usage.

1.1 useState: State hooks

If we need to update the state of the page, we can put it into the useState hook. For example, a click on a button will add 1 to the data.

1
2
3
4
5
6
7
8
9
const [count, setCount] = useState(0);

return (<>
    <p>{ count}</p>
    <button onClick = {
        () => setCount(count + 1)
    }> add 1 </button>
    </>
);

In the typescript system, the type of count is, by default, the type of the current initial value, for example, the variable in the above example is of type number. If we want to customize the type of this variable, we can define it after useState.

1
const [count, setCount] = useState<number | null>(null); // 变量count为number类型或者null类型

Also, when using useState to change the state, the entire state is replaced, so if the state variable is an object type data and I only want to change one of the fields, it will automatically merge the data internally when setState is called from within the previous class component.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Home extends React.Component {
    state = {
        name: 'wenzi',
        age: 20,
        score: 89
    };

    update() {
        this.setState({
            score: 98
        }); // 内部自动合并
    }
}

However, when using useState within a function component, you need to merge the data yourself before calling the method, otherwise the fields will be lost.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const [person, setPerson] = useState({
    name: 'wenzi',
    age: 20,
    score: 89
});

setPerson({
    ...person,
    {
        score: 98
    }
}); // 先合并数据 { name: 'wenzi', age: 20, score: 98 }
setPerson({
    score: 98
}); // 仅传入要修改的字段,后name和age字段丢失

1.2 useEffect: Side effect hooks

useEffect can be thought of as a combination of the functions componentDidMount, componentDidUpdate and componentWillUnmount.

The useEffect hook must be executed once when the component is initialized, and it depends on the 2nd parameter passed in whether or not the component will be updated during the re-rendering process.

  1. when there is only one argument to the callback function, the callback is executed for each update of the component.
  2. when there are 2 parameters, the callback is executed only when the data in the 2nd parameter changes.
  3. to be executed only once when the component has been initialized, the second parameter can be passed to an empty array.

We can see in this example that the useEffect callback is executed whether the add button or the settime button is clicked.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const Home = () => {
    const [count, setCount] = useState(0);
    const [nowtime, setNowtime] = useState(0);

    useEffect(() => {
        console.log('count', count);
        console.log('nowtime', nowtime);
    });

    return ( <>
        <p>count: {count} </p>
        <p>nowtime: {nowtime} </p>
        <button onClick = {() => setCount(count + 1)}> add 1 </button>
        <button onClick = {() => setNowtime(Date.now())} > set now time </button>
    </>);
};

If you change it like this, the callback will be output in the console only when the count changes, and not when the nowtime value is changed.

1
2
3
4
useEffect(() => {
    console.log('count', count);
    console.log('nowtime', nowtime);
}, [count]);

The useEffect callback function can also return a function that is called before the end of the effect’s life cycle. To prevent memory leaks, the clear function is executed before the component is unloaded. Also, if the component is rendered multiple times, the previous effect is cleared before the next effect is executed.

Based on the above code, let’s modify it a bit.

1
2
3
4
5
6
useEffect(() => {
    console.log('count', count);
    console.log('nowtime', nowtime);

    return () => console.log('effect callback will be cleared');
}, [count]);

sobyte

Based on this mechanism, it is particularly suitable for cases where there is a need to add and remove bindings, such as listening to page window size changes, setting timers, establishing and disconnecting from the back-end websocket interface, etc. A secondary wrapping of useEffect can be expected to form a custom hook.

1.3 useMemo and useCallback

The variables and methods defined in the function component are re-calculated when the component is re-rendered, as in the following example.

Translated with www.DeepL.com/Translator (free version)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const Home = () => {
    const [count, setCount] = useState(0);
    const [nowtime, setNowtime] = useState(0);

    const getSum = () => {
        const sum = ((1 + count) * count) / 2;
        return sum + ' , ' + Math.random(); // 这个random是为了看到区别
    };

    return ( <>
        <p> count: {count}< /p>
        <p> sum: {getSum()}</p>
        <p> nowtime: {nowtime}</p>
        <button onClick = {() => setCount(count + 1)} > add 1 </button>
        <button onClick = {() => setNowtime(Date.now())}> set now time </button>
    </>);
};

There are 2 buttons here, one for count+1 and one for setting the current timestamp. The getSun() method calculates the sum from 1 to count, and the sum method will recalculate the sum every time we click the add button. However, when we click the settime button, the getSum method will also recalculate, which is not necessary.

Here we can use useMemo to change it.

1
2
3
const sum = useMemo(() => ((1 + count) * count) / 2 + ' , ' + Math.random(), [count]);

<p> {sum} </p>;

click for sample. Once modified, you can see that the sum value is only recalculated when the count changes, but not when the settime button is clicked. This is thanks to the useMemo hook feature.

1
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useMemo returns the value returned in the callback, and memoizedValue it is only recalculated when a dependency changes. This optimization helps to avoid high overhead calculations at each rendering. If an array of dependencies is not provided, useMemo will compute the new value each time it renders.

In the above example, sum is only recalculated when the count variable changes, otherwise the value of sum remains the same.

useCallback is the same type as useMemo, except that useCallback returns a function, e.g.

1
2
3
const fn = useCallback(() => {
    return ((1 + count) * count) / 2 + ' , ' + nowtime;
}, [count]);

2. implement a few custom hooks

In the official documentation, the online and offline functionality for friends is implemented. Here we also learn to implement a few hooks ourselves.

2.1 Get the width and height of the window change

We listen to the resize event to get the width and height of the window in real time, and encapsulate this method to automatically unbind the resize event before the end of its lifecycle.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const useWinResize = () => {
    const [size, setSize] = useState({
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
    });
    const resize = useCallback(() => {
        setSize({
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
    })
    }, [])
    useEffect(() => {
        window.addEventListener('resize', resize);
        return () => window.removeEventListener('resize', resize);
    }, []);
    return size;
}

It is also very easy to use.

1
2
3
4
5
6
7
8
const Home = () => {
    const {width, height} = useWinResize();

    return <div>
        <p>width: {width}</p>
        <p>height: {height}</p>
    </div>;
};

Click on the link use of useWinResize to see the demo demonstration.

2.2 Timer useInterval

When using a timer in a front-end, you usually have to clear the timer before the end of the component’s life cycle, and if the timer’s period has changed, you also have to clear the timer before restarting it according to the new period. The most common scenario of this kind is the nine-gallery lottery, where the user clicks to start the lottery, then starts slowly, then gradually gets faster, and after the interface returns the winning result, then starts to slow down and finally stops.

It’s easy to think of using useEffect to implement such a hook.

1
2
3
4
5
6
7
8
const useInterval = (callback, delay) => {
    useEffect(() => {
        if (delay !== null) {
            let id = setInterval(callback, delay);
            return () => clearInterval(id);
        }
    }, [delay]);
};

Let’s try this code in our project.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const Home = () => {
    const [count, setCount] = useState(0);

    useInterval(() => {
        console.log(count);
        setCount(count + 1);
    }, 500);

    return <div > {
        count
    } < /div>;
};

The output of console.log(count) indicates that the code is not stuck, so what’s the problem?

The props and states in React components can change, and React re-renders them and “discards” any results from the last rendering, so they are no longer relevant to each other.

The useEffect() Hook also “discards” the result of the previous rendering, it clears the previous effect and creates the next effect, which locks in the new props and state, which is why our first attempt at a simple example worked correctly.

But setInterval doesn’t “throw away”. It keeps referring to the old props and state until you replace it – which you can’t do without resetting the time.

Here’s where the useRef hook comes in. We store the callback in the ref and update the value of ref.current is updated when the callback is updated.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const useInterval = (callback, delay) => {
    const saveCallback = useRef();

    useEffect(() => {
        // 每次渲染后,保存新的回调到我们的 ref 里
        saveCallback.current = callback;
    });

    useEffect(() => {
        function tick() {
            saveCallback.current();
        }
        if (delay !== null) {
            let id = setInterval(tick, delay);
            return () => clearInterval(id);
        }
    }, [delay]);
};

When we use the new useInterval, we find that it is self-increasing, click for a sample simple use of useInterval.

Here we use a variable to control the rate of increase.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const [count, setCount] = useState(0);
const [diff, setDiff] = useState(500);

useInterval(() => {
    setCount(count + 1);
}, diff);

return ( <div>
    <p> count: {count} </p>
    <p> diff: {diff}ms </p> 
    <p>
        <button onClick = {() => setDiff(diff - 50)}> 加快50ms </button> 
        <button onClick = {() => setDiff(diff + 50)} > 减慢50ms </button>
    </p>
</div>);

Click the two buttons separately to adjust the rate of count increase.

3. Summary

There are a lot of interesting things you can do with react hook, here we just give a few simple examples, we will also go deeper into the principle of hook.