React Hooks

React Hooks

what is hooks

React component is divided into class component and function component, However, compared with class component, using functional component has more advantages and benefits, but in the past, when component needed to manage its own state or lifecycle, it had to use class to build.

Now the birth of hooks allows the general public to manage or share state and lifecycle logic through the hooks API provided by React in the functional component. the following will briefly introduce a few commonly(recently used) hooks.

useState It is the state, in the class component, you will define the state in the constructor and use this state and this.setState to read and write, like

class Counter extends React.Component {
    constructor () {
        super()
        this.state = {
            count: 0
        }
    }
    this.handleClick = this.handleClick.bind(this)

    handleClick() {
        this.setState({
            count: this.state.count + 1
          })
    }

    render() {
        return (
            <span>
                count: {this.state.count}
            </span>
        )
    }
}

Here we define a count state to show that the component has been clicked serveral times, but with the useState in Hooks we can rewrite it as.

import React, { useState } from 'react';
const Counter = () => {
    const [count, setCount] = useState(0)

    const handleClick = () => {
        setCount(count + 1)
    }
    return (
        <span>
            count: {count}
            <button onClick={handleClick}>click</button>
        </span>
    )
}

First, we use useState to create a state named count and give it an inital value of 0, Use array destructuring to obtain the first two elements of the returned array. Declare count as the current value of the state and change it. The function setCount of the state value, so that you can directly use count and setCount in the component to read and write this state, and you need to manage multiple states, you can use useState() multiple times.

In addition, the update function obtained from useState can also be passed into the updater function to update the state, such as the previous this.setSate(prevCount => prveCount + 1)

However, if the current state value is used to update the state, re-render and effects will not be triggered, such as setCount(count)

useEffect componentDidMount、 componentDidUpdate、componentWillUnmount . when the component renders each time(browser rendering is completed), the side effect callback function of useEffect will be triggered.

import React, { useState, useEffect } from 'react';
const Counter = () => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log(`Counter rendered, and current count is ${count}`);
  });
  return (
    <span onClick={() => setCount(count + 1)}>
      count: {count}
    </span>
  );
};

As above, now whenever mount or click to change the state count, the re-render will trigger the effect we defined, which simplifies the situation that may need to handle the same logic in componentDidMount and componentDidUpdate in the past.

At the same time, we can split the unrelated side effect logic from the original lifecycle method into multiple useEffect.

in addition, you can also dd the logic of clean up useEffect. For example, addEventListener in componentDidMount will process rremoveEveentListener in componentWillUnMount, and even when props change, you need to re-subscribe based on props, and you may remove and add in componentDidUpdate first.

However, using useEffect to handle this kind of logic such as remove/unsubsribe only needs to return a clean up effect function in the effect function.React will execute the clean up effect function before each render is ready to execute the effect function.

useEffect(() => {
 // after every render,
  // like componentDidMount and componentDidUpdate
  return () => {
       // exec before running the effects next time
  }
})

But often we may only want to execute the effect function when componentDidMount, componentWillUnmount or specific props change. At this time, we can use the second parameter (array of dependencies) of useEffect to specify that this will only be executed when specific variables, props, and state change. effect function, and when an empty array is given, the effect function can be regarded as componentDidMount; the clean up effect function is componentWillUnmount

import React, { useState, useEffect } from 'react';
const Counter = ({ someProps }) => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log('componentDidMount');
    return () => {
      console.log('componentWillUnmount');
    }
  }, []);
  useEffect(() => {
    console.log('only fired when `count` changed');
  }, [count]);
  useEffect(() => {
    console.log('only fired when `someProps` changed');
  }, [someProps]);
  return (
    <span onClick={() => { setCount(count + 1); }}>
      Count: {count}
    </span>
  );
};

useContext Not surprisingly, context makes it easier to communicate between components that span multiple layers. You don’t have to pass data down through props all the time. In the past, you might use context APIs like this to prevent intermediate components from helping to pass data that is not relevant to you.

// MyContext
const MyContext = React.createContext()
export const MyConsumer = MyContext.consumer
export class MyProvider extends React.Component {
    render() {
        return(
          <MyContext.Provider value={this.props.value} >
               {this.props.children }
          </MyContext.Provider>
        )
    }
}

Establish the Provider and Consumer of the context first, and then

// App.js
import { MyProvider, MyConsumer } from  './MyContext'

const DeepComponent = () => (
   <MyConsumer>
        { value => <span> context value: {value} </span> }
   </MyConsumer>

)

const SomeComponent = () => (
  <div>
    <DeepComponent />
  </div>
);

const App = () => {
   <MyProvider value='foo'>
    <div>
         <SomeComponent />
    </div>
   </MyProvider>
}

Put the Provider and Consumer where needed to bridge the data. As above, we can directly get the value of 'foo' provided by the uppermost App in the deepest DeepComponent, and now it can be given to React.createContext through useContext. The returned object gets the value in the context, like:

// MyContext.js
export const MyContext = React.createContext();
export class MyProvider extends React.Component {
  render() {
    return (
      <MyContext.Provider value={this.props.value}>
        { this.props.children }
      </MyContext.Provider>
    );
  }
}

Then use useContext to get the value of context

import React, { useContext } from 'react';
import { MyContext, MyProvider } from './MyContext'
const DeepComponent = () => {
  const value = useContext(MyContext);
  return <span>context value: {value}</span>;
};
const SomeComponent = () => (
  <div>
    <DeepComponent />
  </div>
);
const App = () => (
  <MyProvider value="foo">
    <div>
      <SomeComponent />
    </div>
  </MyProvider>
);

useCallback Let's take a look at a piece of code before introducing useCallback

import React, { useState } from "react";
import ReactDOM from "react-dom";
const Button = ({ onClick }) => <button onClick={onClick}>Submit</button>;

const App = () => {
  const [text, setText] = useState("");
  const handleChange = e => {
    setText(e.target.value);
  };

  const handleSubmit = () => {
    console.log(text);
  };

  return (
    <div>
      <input type="text" onChange={handleChange} value={text} />
      <Button onClick={handleSubmit} />
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

This is a very simple controlled input plus a button with a click event listener. At first glance, there is no problem, but in fact, every time the user triggers onChange in the input, it will change the state. However, Will be re-rendered, which sounds reasonable, but when re-render, the input and in it will be re-rendered, and you have thought that will be re-rendered for no reason. Re-render feeling?

At this point, you might think of reducing unnecessary render through React.PureComponent or React.memo, like

const  Button = React.memo(( {onClick}) => (
   <button onClick={onClick}> Submit </button>
))

But continues to be re-rendered

Yes, although PureComponent or memo will make shallow comparisons on the incoming props, and update only when there are differences, and the only props onClick of looks the same every time, but in fact, every time When render, a new handleSubmit function instance will be generated and plugged into , so even if React.memo is used, will re-render when the user changes the input.

At this time, we need to have a method so that handleSubmit, which should be the same every time, will not generate a new instance due to re-render, which is the protagonist of this section useCallback

Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.

We can get a memoized function through useCallback, and every time the component renders, we will compare whether the dependencies have changed. If there is, a new function instance will be generated, so our code becomes like this

 import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";

const Button = React.memo(({ onClick }) => (
  <button onClick={onClick}>Submit</button>
));

const App = () => {
  const [text, setText] = useState("");
  const handleChange = e => {
    setText(e.target.value);
  };

  const handleSubmit = useCallback(() => {
    console.log(text);
  }, []);

  return (
    <div>
      <input type="text" onChange={handleChange} value={text} />
      <Button onClick={handleSubmit} />
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

There really won't be any extra render, but what seems weird? No matter what you input, after pressing Submit, the console log will show an empty string The reason is that our dependencies array is empty, so this memoized handleSubmit is generated during the first render of and will not change anymore, and because of this, only the current one can be accessed. state state, which is the initial value of text'' However, in order to obtain and print the latest text state correctly, a new memoized function instance must be generated when the text changes, that is, add text in the dependencies

const handleSubmit = useCallback(() => {
  console.log(text);
}, [text]);

In this scenario, because there is only one state of text, it turns into a circle and becomes re-render every time renders , but we will use useRef to solve this problem in the next section.

useRef when you see ref, you may think of the ref that you will use when accessing the DOM. yes, you can get the element node you want through useRef.

import React,{useRef } from "react"
import ReactDOM from "react-dom"

const App =() => {
   const inputRef = useRef(null)
   const handleClick = () => {
      inputRef.current.focus()
   }

   return (
      <div>
         <input type="text" ref={inputRef} />
          <button
              onClick={handClick}
           >
              click me to focvus input
         </button>
      </div>
   )
}

const rootElement = document.getElementById("root")
ReactDOM.render(<App />, rootElement)

First, we can get a ref object with an initial value of null through useRef(null). After passing this object into the ref of input, we can get the latest value through the current property in this ref object, which is the input DOM node

The returned object will persist for the full lifetime of the component. Essentially, useRef is like a “box” that can hold a mutable value in its .current property.

In addition, useRef can also be used like an instance variable in a class component, directly accessing arbitrary data through .current in the ref object, and when you change .current, it will not cause component re-render. This way We can use useRef to solve the last re-render problem in the previous section

import React, {useRef} from "react";
import ReactDOM from "react-dom";

constButton = React.memo(({onClick}) => (
    <button onClick={onClick}>Submit</button>
))

const App =() => {
   const textRef = useRef("")
   const handChange  = e => {
     textRef.current = e.target.value
   }

   const handSubmit = useCallback(() => {
     console.log(textRef.current)
   }, [])

    return (
    <div>
      <input type="text" onChange={handleChange} />
      <Button onClick={handleSubmit} />
    </div>
  );
}

First, change the input to uncontrolled and use the ref object of textRef to store the value after each input onChange, and then use textRef.current to get the latest value when the button is onClick, which is the content in the current input, so will no longer be re-render due to unnecessary changes to handleSubmit and at the same time can correctly get the value entered by the userc

Custom Hooks In addition to React's native Hooks API, when repeated logic appears in multiple components, common state, effects, etc. can also be extracted into self-made hooks. Think of it as a component that only manages state and lifecycle

For example, when many components have to deal with the input value, it can be written as a useInput or something like

import React from "react"
import ReactDOM from "react-dom"
import useInput from "./inputHook"

const App = () => {
   const [textRef, handChange ] = useInput()
   return (
    <div>
      <input type="text" onChange={handleChange} />
      <button
        onClick={() => {
          console.log(textRef.current);
        }}
      >
        log input value
      </button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
import {useRef, useCallback } from "react"

const useInput = (initialText = "") => {
    const textRef = useRef(initialText)
    const handleChange = useCallback(() => {
      textRef.current = e.target.value
    }, [])

    return [textRef, handleChange]
}

export default useInput

A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.

It should be noted that custom Hooks need to start with use as the name, and you can also refer to other third-party custom Hooks

Precautions

Don’t call Hooks inside loops, conditions, or nested functions.

React relies on the order in which Hooks are called Ensure that Hooks are called in the same order each time a component renders

Don’t call Hooks from regular JavaScript functions. But you can wrap Hooks logic into custom Hooks to use

Pay attention to the correct dependencies when using Hooks

Make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect.

Every value referenced inside the effect function should also appear in the dependencies array

The variables used in the component scope in Hooks should be placed in the dependency array, and if the function called in the Hooks uses component scope variables such as state, props, etc., it is recommended to move the function to Hooks. Have a clear grasp of dependencies