This series will cover the use of React Hooks, starting with useState, and will include the following:

  • useState
  • useEffect
  • useContext
  • useReducer
  • useCallback
  • useMemo
  • useRef
  • custom hooks

Mastering the React Hooks API will help you better use it in your work and get to the next level of mastering React. This series uses a lot of sample code and effects that are easy to use for beginners and review.

If you know how to write React Class, you need to know how to write setState() and props.

Let’s start with the first example.

Simple example of a usestate-counter

Click the +1 counter

Class component

CounterClass.tsx

import React, { Component } from 'react'

class CounterClass extends Component {

  state = {
    count: 0
  }

  incrementCount = () = > {
    this.setState({
      count: this.state.count + 1})}render() {
    const { count } = this.state
    return (
      <div>
        <button onClick={this.incrementCount}>Count {count}</button>
      </div>)}}export default CounterClass

Copy the code

App.tsx

import React from 'react'

import './App.css'

import CounterClass from './components/CounterClass'

const App = () = > {
  return (
    <div className="App">
      <CounterClass />
    </div>)}export default App
Copy the code

Results the following

Creating such a counter is a simple three-step process

  1. Create a Class component
  2. Create the state
  3. Create increment method

How to use Function Component and State Hook implementation

The State Hook to achieve

HookCounter.tsx

import React, { useState } from 'react'

function HookCounter() {
  const [count, setCount] = useState(0)
  return (
    <div>
      <button onClick={()= > {
        setCount(count + 1)
      }}>Count {count}</button>
    </div>)}export default HookCounter
Copy the code

App.tsx

import React from 'react'

import './App.css'

// import CounterClass from './components/1CounterClass'
import HookCounter from './components/1HookCounter'

const App = () = > {
  return (
    <div className="App">
      <HookCounter />
    </div>)}export default App

Copy the code

The effect is the same as the Class component

Let’s look at how to use useState

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

This line of code returns a state and a function to update 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 enqueues a rerendering of the component.

Summarize the rules of use of Hooks

  • Hooks can only be invoked at the top scope
    • Cannot be used in inner loops, conditional judgments, or nested methods
  • You can only use Hooks in React Function
    • You cannot use Hooks in other normal functions

useState with Previous State

This section describes how to use previous State, which is used when your state value depends on a previous state value.

Again with the Counter example, add a button click +1 or -1

Counter sample

HookCounter.tsx

import React, { useState } from 'react'

function HookCounter() {

  const initialCount = 0
  const [count, setCount] = useState(initialCount)

  return (
    <div>
      Count: {count}
      <button onClick={()= > {
        setCount(initialCount)
      }}>Reset</button>
      <button onClick={()= > {
        setCount(count + 1)
      }}> + 1 </button>
      <button onClick={()= > {
        setCount(count - 1)
      }}> - 1 </button>
    </div>)}export default HookCounter
Copy the code

App.tsx

import React from 'react'

import './App.css'

import HookCounter from './components/3HookCounter'

const App = () = > {
  return (
    <div className="App">
      <HookCounter />
    </div>)}export default App
Copy the code

The effect is as follows:

It looks fine, but it’s not safe to write like this! This is not the correct way to change a counter. Here’s why:

Again for example, add another button to the above example, adding 5 at a time

The following code

import React, { useState } from 'react'

function HookCounter() {

  const initialCount = 0
  const [count, setCount] = useState(initialCount)

+ const increment5 = () => {
+ for (let i = 0; i < 5; i++) {
+ setCount(count + 1)
+}
+}

  return (
    <div>
      Count: {count}
      <button onClick={() => {
        setCount(initialCount)
      }}>Reset</button>
      <button onClick={() => {
        setCount(count + 1)
      }}> + 1 </button>
      <button onClick={() => {
        setCount(count - 1)
      }}> - 1 </button>
+ 
    </div>
  )
}

export default HookCounter
Copy the code

When I hit + 5, I only added 1

This is because the setCount method is asynchronous and cannot react and update immediately, and the count in the instantaneously activated multiple entries is still the old value and has not been updated.

Modify the code as follows:

import React, { useState } from 'react'

function HookCounter() {

  const initialCount = 0
  const [count, setCount] = useState(initialCount)

  const increment5 = () => {
    for (let i = 0; i < 5; i++) {
+ setCount(prevCount => prevCount + 1)
    }
  }

  return (
    <div>
      Count: {count}
      <button onClick={() => {
        setCount(initialCount)
      }}>Reset</button>
      <button onClick={() => {
        setCount(count + 1)
      }}> + 1 </button>
      <button onClick={() => {
        setCount(count - 1)
      }}> - 1 </button>
      <button onClick={increment5}> + 5 </button>
    </div>
  )
}

export default HookCounter
Copy the code

If you want to use the previous state, you need to pass the value in function and return the new value after change. Modify the + 1-1 function as well. The code after improvement is as follows:

import React, { useState } from 'react'

function HookCounter() {

  const initialCount = 0
  const [count, setCount] = useState(initialCount)

  const increment5 = () = > {
    for (let i = 0; i < 5; i++) {
      setCount(prevCount= > prevCount + 1)}}return (
    <div>
      Count: {count}
      <button onClick={()= > {
        setCount(initialCount)
      }}>Reset</button>
      <button onClick={()= > {
        setCount(prevCount => prevCount + 1)
      }}> + 1 </button>
      <button onClick={()= > {
        setCount(prevCount => prevCount - 1)
      }}> - 1 </button>
      <button onClick={increment5}> + 5 </button>
    </div>)}export default HookCounter
Copy the code

summary

When previousState is used, it is passed to the setState method using the setter function. To ensure that the correct previous state is obtained.

In the re-render, the first value returned by useState will always be the latest updated state.

useState with Object

There are some caveat to calling setState when the state in useState is an object. UseState does not automatically merge the updated object. You can do this using the functional setState combined with the expansion operator.

Wrong example firstName & lastName

HookCounter.tsx

import React, { useState } from 'react'

function HookCounter() {

  const [name, setName] = useState({
    firstName: ' '.lastName: ' '
  })

  return (
    <form>
      <input
        type="text"
        value={name.firstName}
        onChange={e= > {
          setName({
            firstName: e.target.value
          })
        }}
      />
      <input
        type="text"
        value={name.lastName}
        onChange={e= > {
          setName({
            lastName: e.target.value
          })
        }}
      />
      <h2>Your first name is {name.firstName}</h2>
      <h2>Your last name is {name.lastName}</h2>
    </form>)}export default HookCounter
Copy the code

Notice that onChange on the input tag, each time you setName, only one property in the object is being operated on. The lastName property disappears when only firstName is assigned, which is a wrong way to write it.

Since I’m using TSX to write components, my compiler is reporting an error:

The browser also directly reported an error

Correct example – Manually merge objects

The expansion operator is used here to solve the problem of this object

import React, { useState } from 'react'

function HookCounter() {

  const [name, setName] = useState({
    firstName: ' '.lastName: ' '
  })

  return (
    <form>
      <input
        type="text"
        value={name.firstName}
        onChange={e= >{ setName({ ... name, firstName: e.target.value }) }} /><input
        type="text"
        value={name.lastName}
        onChange={e= >{ setName({ ... name, lastName: e.target.value }) }} /><h2>Your first name is {name.firstName}</h2>
      <h2>Your last name is {name.lastName}</h2>
      <h2>{JSON.stringify(name)}</h2>
    </form>)}export default HookCounter
Copy the code

summary

When the object is operated in state Hook, the attributes in the object will not be automatically merged. We need to manually merge the objects, and we can use the expansion operator.

So, arrays are similar, see the next section.

useState with Array

The list of the sample

Click the button to add a random number 1-10 to the list

UseStateWithArray.tsx

import React, { useState } from 'react'

interface ItemType {
  id: number
  value: number
}

function UseStateWithArray() {
  const [items, setItems] = useState<ItemType[]>([])

  const addItem = () = > {
    setItems([
      ...items,
      {
        id: items.length,
        value: Math.ceil(Math.random() * 10)})}return (
    <div>
      <button onClick={addItem}>add a number</button>
      <ul>
        {
          items.length > 0 && items.map((item: ItemType) => (
            <li key={item.id}>{item.value}</li>))}</ul>
    </div>)}export default UseStateWithArray
Copy the code

The effect is as follows:

Note how typeScript is used in the hooks, as shown in the following article

  • Use React hooks in TypeScript

UseState summary

So much for the use of useState, here is a little summary.

  • You can use state in functional components
  • In a class component, state is an object, but in useState, state can be anything but an object
  • UseState returns an array of two elements
    • The first is the current value of state
    • The second is the setter method for state, which triggers Rerender when called
      • If the current state depends on the previous state, you can pass a function in the setter for state that takes the previous state and returns the new state
  • For objects and arrays, note that old variables are not automatically completed in state; you need to manually replenish them yourself using the expansion operator

This is the end of useState. In the next article, we will learn the use of useEffect.