Learn how to use React Hooks. Learn how to use React Hooks by learning how to use React Hooks

preface

In the React world, there are container components and UI components. Before the React Hooks, UI components could use functions, stateless components to display THE UI, while function components were useless for container components. We relied on class components to get data and process data. And pass parameters down to the UI component for rendering. In my opinion, using React Hooks has the following advantages over previous class components:

  1. The code is more readable, and the original code logic of the same function is split into different life cycle functions, which makes it difficult for developers to maintain and iterate. With React Hooks, the function code can be grouped together for easy maintenance
  2. In the original code, we often used HOC/render props to replicate the state of the component and to enhance functions, which increased the number of layers in the component tree. In React Hooks, these functions can be implemented using powerful custom Hooks

React Hooks feature is available in v16.8. Although the community doesn’t have a best practice on how to build complex applications based on React Hooks (at least I haven’t), by reading a number of articles in the community on the subject, Here are 10 examples that will help you understand and become familiar with most of the features of React Hooks.

UseState saves component state

In class components, we use this.state to hold the component state and modify it to trigger component rerendering. This simple counter component, for example, is a good example of how class components work: online Demo

import React from "react"; class App extends React.Component { constructor(props) { super(props); this.state = { count: 0, name: "alife" }; } render() { const { count } = this.state; return ( <div> Count: {count} <button onClick={() => this.setState({ count: count + 1 })}>+</button> <button onClick={() => this.setState({ count: count - 1 })}>-</button> </div> ); }}Copy the code

A simple counter component is done. In the function component, React uses useState to save the state of the component because there is no dark magic called this. The online Demo

import React, { useState } from "react"; function App() { const [obj, setObject] = useState({ count: 0, name: "alife" }); return ( <div className="App"> Count: {obj.count} <button onClick={() => setObject({ ... obj, count: obj.count + 1 })}>+</button> <button onClick={() => setObject({ ... obj, count: obj.count - 1 })}>-</button> </div> ); }Copy the code

Return an array with a default state and a function to change the state by passing the useState argument. Change the original state value by passing a new state to the function. It is worth noting that useState does not help you handle state, as compared to setState non-override updates, useState override updates require the developer to handle the logic. (Code above)

It seems that with a useState function component can have its own state, but that’s not enough.

UseEffect Handles side effects

React provides useEffect to help developers deal with the side effects of function components. Before introducing the new API, let’s look at how class components work

import React, { Component } from "react"; class App extends Component { state = { count: 1 }; componentDidMount() { const { count } = this.state; document.title = "componentDidMount" + count; this.timer = setInterval(() => { this.setState(({ count }) => ({ count: count + 1 })); }, 1000); } componentDidUpdate() { const { count } = this.state; document.title = "componentDidMount" + count; } componentWillUnmount() { document.title = "componentWillUnmount"; clearInterval(this.timer); } render() { const { count } = this.state; return ( <div> Count:{count} <button onClick={() => clearInterval(this.timer)}>clear</button> </div> ); }}Copy the code

In the example, the component updates the component state every second, and every time the update is triggered, document.title is updated (side effect), and document.title is modified (similar to cleanup) when the component is unloaded.

As you can see from the examples, some of the repetitive features developers need to write repeatedly at componentDidMount and componentDidUpdate, which is completely different if you use useEffect. The online Demo

import React, { useState, useEffect } from "react"; let timer = null; function App() { const [count, setCount] = useState(0); useEffect(() => { document.title = "componentDidMount" + count; },[count]); useEffect(() => { timer = setInterval(() => { setCount(prevCount => prevCount + 1); }, 1000); // Tell react to call return () => {document.title = "componentWillUnmount"; clearInterval(timer); }; } []); return ( <div> Count: {count} <button onClick={() => clearInterval(timer)}>clear</button> </div> ); }Copy the code

The first parameter, useEffect, receives a function that performs side effects such as asynchronous requests and changes to external parameters. The second parameter, called Dependencies, is an array. The function in the first argument to useEffect is triggered only if the values in the array change. The return value, if any, is called before the component is destroyed or the function is called.

  • 1. In the first useEffect, you modify documen.title as soon as count changes;
  • UseEffect = componentDidMount (); useEffect = componentWillUnmount (); useEffect = componentDidMount (); useEffect = componentWillUnmount ();
    1. There is another case where the second argument is not passed, where useEffect only receives the first function argument, meaning that no parameter changes are listened for. After each DOM rendering, the function in useEffect is executed.

With this powerful Hooks, we can emulate and encapsulate other life cycle functions, such as componentDidUpdate code is very simple

Function useUpdate(fn) {// useRef Creates a reference const mounting = useRef(true); useEffect(() => { if (mounting.current) { mounting.current = false; } else { fn(); }}); }Copy the code

Now that we have useState management state, useEffect handling side effects, and asynchronous logic, learning these two tricks is enough to handle most of the usage scenarios of the class components.

UseContext reduces component hierarchy

UseState and useEffect are two of the most basic apis used by React. UseContext is used to handle multi-layer data transfer. In the past, when a component tree or a cross-layer ancestor wanted to send data to its grandchild, the useContext API was used to handle multi-layer data transfer. In addition to passing layers of props down, we can also use the React Context API to do this for us, as a simple example: an online Demo

const { Provider, Consumer } = React.createContext(null);
function Bar() {
  return <Consumer>{color => <div>{color}</div>}</Consumer>;
}
function Foo() {
  return <Bar />;
}
function App() {
  return (
    <Provider value={"grey"}>
      <Foo />
    </Provider>
  );
}
Copy the code

The React createContext syntax allows you to pass data across Foo in the APP component to the Bar. In React Hooks we can do this using useContext. The online Demo

const colorContext = React.createContext("gray");
function Bar() {
  const color = useContext(colorContext);
  return <div>{color}</div>;
}
function Foo() {
  return <Bar />;
}
function App() {
  return (
    <colorContext.Provider value={"red"}>
      <Foo />
    </colorContext.Provider>
  );
}
Copy the code

UseContext is passed the context, not the consumer, and the return value is the data that you want to pass through. The usage is simple, useContext can solve the problem of Consumer multi-state nesting. The reference example

function HeaderBar() {
  return (
    <CurrentUser.Consumer>
      {user =>
        <Notifications.Consumer>
          {notifications =>
            <header>
              Welcome back, {user.name}!
              You have {notifications.length} notifications.
            </header>
          }
      }
    </CurrentUser.Consumer>
  );
}
Copy the code

Using useContext is much cleaner, more readable, and does not add depth to the component tree.

function HeaderBar() {
  const user = useContext(CurrentUser);
  const notifications = useContext(Notifications);
  return (
    <header>
      Welcome back, {user.name}!
      You have {notifications.length} notifications.
    </header>
  );
}
Copy the code

useReducer

UseReducer: this Hooks are almost identical to Redux/ React-redux in use, the only thing missing is the inability to use the middleware provided by Redux. We rewrote the timer component above as a useReducer, an online Demo

import React, { useReducer } from "react";
const initialState = {
  count: 0
};
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - action.payload };
    default:
      throw new Error();
  }
}
function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "increment", payload: 5 })}>
        +
      </button>
      <button onClick={() => dispatch({ type: "decrement", payload: 5 })}>
        -
      </button>
    </>
  );
}
Copy the code

The usage is basically the same as Redux, the usage is also very simple, kind of provides a mini version of Redux.

UseCallback memory function

In class components, we often make the following mistakes:

class App { render() { return <div> <SomeComponent style={{ fontSize: 14 }} doSomething={ () => { console.log('do something'); }} /> </div>; }}Copy the code

What’s the harm in writing that? Any time the props or state of the App component changes, it triggers a rerender, even if it is unrelated to the SomeComponent component, because each render generates a new style and doSomething. Style and doSomething refer to different references), so SomeComponent is re-rendered. If SomeComponent is a large component tree, this Virtual Dom comparison is obviously wasteful. The solution is also very simple, to extract the parameters into variables.

const fontSizeStyle = { fontSize: 14 }; class App { doSomething = () => { console.log('do something'); } render() { return <div> <SomeComponent style={fontSizeStyle} doSomething={ this.doSomething } /> </div>; }}Copy the code

In class components, we can also store functions through the this object, whereas in function components there is no way to mount them. So the function component will re-render its child component every time it renders if there is a transfer function.

function App() {
  const handleClick = () => {
    console.log('Click happened');
  }
  return <SomeComponent onClick={handleClick}>Click Me</SomeComponent>;
}
Copy the code

The first version interprets functional components as syntactic sugar of the Class component’s render function, so all the code inside a functional component is re-executed each time it is re-rendered. HandleClick is a new reference every time we render it, so the props. OnClick passed to the SomeComponent component is changing. Function components re-render child components each time they are rendered if there is a transfer function.

With useCallback, however, you can retrieve a memorized function.

function App() { const memoizedHandleClick = useCallback(() => { console.log('Click happened') }, []); Return <SomeComponent onClick={memoizedHandleClick}>Click Me</ memoecomponent >; }Copy the code

As usual, the second argument is passed into an array, and whenever the value or reference of each item in the array changes, useCallback returns a new memory function for later rendering.

As long as the child component inherits PureComponent or uses React. Memo, unnecessary VDOM rendering can be avoided.

UseMemo memory component

The functionality of useCallback can be completely replaced by useMemo if you want to return a memory function using useMemo.

useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs).

Copy the code

So the previous example using useCallback can be overwritten using useMemo:

function App() { const memoizedHandleClick = useMemo(() => () => { console.log('Click happened') }, []); Return <SomeComponent onClick={memoizedHandleClick}>Click Me</ memoecomponent >; }Copy the code

The only difference is that **useCallback does not execute the first argument function, but returns it to you, whereas useMemo executes the first function and returns the result of its execution to you. ** So in the previous example, you can return handleClick for the purpose of storing the function.

So useCallback usually remembers the event function, generates the remembered event function and passes it to the child component for use. UseMemo, on the other hand, is more suitable for calculating a definite value by functions, such as memory components.

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => <Child2 b={b} />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
}
Copy the code

Child1 /child2 is rerendered when a/b changes. As you can see from the example, child component updates are triggered only if the value of the second parameter array changes.

UseRef saves the reference value

UseRef, like createRef, can be used to generate references to DOM objects, for a simple example: an online Demo

import React, { useState, useRef } from "react";
function App() {
  let [name, setName] = useState("Nate");
  let nameRef = useRef();
  const submitButton = () => {
    setName(nameRef.current.value);
  };
  return (
    <div className="App">
      <p>{name}</p>

      <div>
        <input ref={nameRef} type="text" />
        <button type="button" onClick={submitButton}>
          Submit
        </button>
      </div>
    </div>
  );
}
Copy the code

The value returned by useRef is passed to the component or the DOM’s ref attribute, and the component or actual DOM node can be accessed through the ref.current value. The important point is that the component is also accessible, so that you can do things to the DOM, such as listen for events, etc.

Of course, useRef is more powerful than you might think. UseRef functions a bit like class properties, or you want to record values in a component that can be changed later.

You can bypass Capture Value features with useRef. Ref can be thought of as a unique reference in all Render processes, so all assignments or values to ref get only one final state, without isolation between Render processes. Function VS Class Components

React Hooks: Live Demo: Capture Value

function App() { const [count, setCount] = useState(0); useEffect(() => { setTimeout(() => { alert("count: " + count); }, 3000); }, [count]); Return (<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> increase count</button> <button OnClick ={() => setCount(count - 1)}> reduce count</button> </div>); }Copy the code

Click to add button first, then click to reduce button, 3 seconds later alert 1, then alert 0, instead of alert 0 twice. This is the so-called Capture Value feature. In the class component, the modified value is output after 3 seconds, because the ** message is mounted to the this variable, which retains a reference value **. Any access to this property will get the latest value. By creating a reference, useRef can circumvent the Capture Value feature in React Hooks. UseRef Avoid Capture Value online Demo

function App() { const count = useRef(0); const showCount = () => { alert("count: " + count.current); }; const handleClick = number => { count.current = count.current + number; setTimeout(showCount, 3000); }; Return (<div> <p>You clicked {count. Current} times</p> <button onClick={() => handleClick(1)}> Add count</button> <button OnClick = {() = > handleClick (1)} > reduce count < / button > < / div >). }Copy the code

By changing the assignment and value object to useRef instead of useState, you can bypass capture Value and get the latest value three seconds later.

UseImperativeHandle passthrough Ref

UseImperativeHandle is used to let the parent component get the online Demo of the index within the child component

import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react"; function ChildInputComponent(props, ref) { const inputRef = useRef(null); useImperativeHandle(ref, () => inputRef.current); return <input type="text" name="child input" ref={inputRef} />; } const ChildInput = forwardRef(ChildInputComponent); function App() { const inputRef = useRef(null); useEffect(() => { inputRef.current.focus(); } []); return ( <div> <ChildInput ref={inputRef} /> </div> ); }Copy the code

In this way, the App component can get the DOM node of the child component’s input.

UseLayoutEffect Synchronization execution side effect

In most cases, using useEffect will help you handle component side effects, but if you want to invoke side effects synchronously, such as DOM operations, you need to use useLayoutEffect, Side effects in useLayoutEffect are executed synchronously after DOM updates. The online Demo

function App() {
  const [width, setWidth] = useState(0);
  useLayoutEffect(() => {
    const title = document.querySelector("#title");
    const titleWidth = title.getBoundingClientRect().width;
    console.log("useLayoutEffect");
    if (width !== titleWidth) {
      setWidth(titleWidth);
    }
  });
  useEffect(() => {
    console.log("useEffect");
  });
  return (
    <div>
      <h1 id="title">hello</h1>
      <h2>{width}</h2>
    </div>
  );
}
Copy the code

In the above example, useLayoutEffect fires synchronously after render and DOM updates, which is better than useEffect asynchronously.

What is the difference between useEffect and useEffect?

UseLayoutEffect and componentDidMount&componentDidUpdate, react DOM update immediately after the synchronization of the call code, will block page rendering. UseEffect is code that will not be called until the entire page has been rendered.

UseEffect is recommended

However, we recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.

In practice, if you want to avoid page jitter (DOM modification is possible in useEffect), you can put the code that needs to manipulate the DOM in useEffect. See git Repository example for page jitter using useEffect

However, useLayoutEffect will cause a warning when rendering on the server. To eliminate this, use useEffect instead or delay rendering. See description and discussion.

Lack of the React Hooks

Although we saw the power of the React Hooks example above, it seems that class components can be completely rewritten using React Hooks. However, the current version of V16.8 does not implement getSnapshotBeforeUpdate and componentDidCatch, two lifecycle functions in class components. Implementation on React Hooks is also officially planned in the near future.

The original address