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

Learn the React Hooks API to help you use React in your work. This series uses a lot of sample code and effect demonstrations, making it easy for beginners and refreshers to use.

Today we will talk about the useEffect.

Why useEffect

Write logic issues in the lifecycle

React’s old lifecycle may have side effects, such as when the title of the page displays the number of clicks:

componentDidMount() {
  document.title = `The ${this.state.count} times`
}
componentDidUpdate() {
  document.title = `The ${this.state.count} times`
}
Copy the code

The same code is written in both componentDidMount and componentDidUpdate, and we can’t mount it once in the lifetime of the component, which leads to code duplication.

In another example, the page contains a countdown and the countdown timer is cleared when the page is destroyed

componentDidMount() {
  this.interval = setInterval(this.tick, 1000)}componentWillUnmount() {
  clearInterval(this.interval)
}
Copy the code

If the component is complex and contains both of the above logic, the code looks like this:

componentDidMount() {
  document.title = `The ${this.state.count} times`
  this.interval = setInterval(this.tick, 1000)}componentDidUpdate() {
  document.title = `The ${this.state.count} times`
}
componentWillUnmount() {
  clearInterval(this.interval)
}
Copy the code

We see two problems

  1. Code duplication. The code to set the title is repeated once
  2. Code dispersion. The logic seems to be scattered throughout the component lifecycle

Therefore, we need a better way to solve it

UseEffect resolves the problem

  • EffectHook solves this problem by performing some of the necessary operations within a functional component
  • Can be considered a replacement for componentDidMount, componentDidUpdate, componentWillUnmount

Next, learn how to use useEffect.

UseEffect After Render

As an example, change the page title by clicking a button

Example of how to write a Class component

7ClassCounter.tsx

import React, { Component } from 'react'

class ClassCounter extends Component {

  state = {
    count: 0
  }

  componentDidMount() {
    document.title = `The ${this.state.count} times`
  }
  componentDidUpdate() {
    document.title = `The ${this.state.count} times`
  }

  render() {
    return (
      <div>
        <button onClick={()= > {
          this.setState({
            count: this.state.count + 1
          })
        }}>
          Clicked {this.state.count} times
        </button>

      </div>)}}export default ClassCounter
Copy the code

App.tsx

import React from 'react'

import './App.css'

import ClassCounter from './components/7ClassCounter'

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

Copy the code

Results the following

Rewrite the above example with useEffect

Next use the function time component to implement the above example

7HookCounter.tsx

import React, { useState, useEffect } from 'react'

function HookCounter() {
  const [count, setCount] = useState(0)

  useEffect(() = > {
    document.title = `${count} times`
  })

  return (
    <div>
      <button onClick={()= > {
        setCount(prevCount => prevCount + 1)
      }} >Clicked {count} times</button>
    </div>)}export default HookCounter
Copy the code

The effect is the same as the Class component

You can see that the first entry to useEffect is an anonymous function that is called after each render. Render is called both on the first render and on subsequent updates.

Also, useEffect is written inside functional components so that you can get props and state directly without writing code like this.

UseEffect is executed conditionally

In the previous section, we learned that useEffect executes functions after each render, which can be a bit of a performance problem, so we’ll look at how to conditionally execute anonymous functions in useEffect.

Build on the example in the previous section to extend the ability to enter a name by determining that only the logic resulting from the count change is executed.

Example of how to write a Class component

import React, { Component } from 'react'

interface stateType {
  count: number
  name: string
}

class ClassCounter extends Component {

  state = {
    count: 0.name: ' ',}componentDidMount() {
    document.title = `The ${this.state.count} times`
  }

  componentDidUpdate(prevProps: any, prevState: stateType) {
    if(prevState.count ! = =this.state.count) {
      console.log('Update title')
      document.title = `The ${this.state.count} times`}}render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.name}
          onChange={(e)= > {
            this.setState({
              name: e.target.value
            })
          }}
        />
        <button onClick={()= > {
          this.setState({
            count: this.state.count + 1
          })
        }}>
          Clicked {this.state.count} times
        </button>
      </div>)}}export default ClassCounter
Copy the code

For better performance, notice that prevState is judged in the code

UseEffect spelled

import React, { useState, useEffect } from 'react'

function HookCounter() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState(' ')

  useEffect(() = > {
    console.log('useEffect - update title')
    document.title = `You clicked ${count} times`
  }, [count])

  return (
    <div>
      <input
        type="text"
        value={name}
        onChange={(e)= > {
          setName(e.target.value)
        }}
      />
      <button onClick={()= > {
        setCount(prevCount => prevCount + 1)
      }} >Clicked {count} times</button>
    </div>)}export default HookCounter
Copy the code

Note that the second argument to useEffect, [count], is an array, and the element is the state or props to be observed. Only when the specified variable changes will the first anonymous function in useEffect be triggered. This helps guarantee performance.

UseEffect is executed only once

This section examines how to useEffect only once using an example of recording mouse coordinates

Record mouse position example Class writing method

import React, { Component } from 'react'

class RunEffectsOnlyOnce extends Component {
  state = {
    x: 0.y: 0
  }

  logMousePos = (e: MouseEvent) = > {
    this.setState({
      x: e.clientX,
      y: e.clientY
    })
  }

  componentDidMount() {
    document.addEventListener('mousemove'.this.logMousePos)
  }

  render() {
    return (
      <div>
        Y - {this.state.y}, X - {this.state.x}
      </div>)}}export default RunEffectsOnlyOnce
Copy the code

Here we’re only doing event binding in componentDidMount, we’re only doing event binding once

UseEffect records mouse coordinates

The above effects are transformed into functional components

import React, { useState, useEffect } from 'react'

function RunEffectsOnlyOnce() {

  const [x, setX] = useState(0)
  const [y, setY] = useState(0)

  const logMousePos = (e: MouseEvent) = > {
    setX(e.clientX)
    setY(e.clientY)
  }

  useEffect(() = > {
    console.log('addEventListener')
    document.addEventListener('mousemove', logMousePos)
  }, [])

  return (
    <div>
      Y - {y}, X - {x}
    </div>)}export default RunEffectsOnlyOnce
Copy the code

Note that the useEffect method passes an empty array as its second argument, effectively avoiding the problem of multiple calls.

If you want to execute effect once (only when the component is mounted and unmounted), you can pass an empty array ([]) as the second argument. This tells React that your effect doesn’t depend on any value in props or state, so it never needs to be repeated. This is not a special case — it still follows the way dependent arrays work.

If you pass an empty array ([]), the props and state inside effect will always have their initial values. Although passing [] as the second argument is closer to the more familiar componentDidMount and componentWillUnmount mind-set, there are better ways to avoid repeating calls to Effect too often. In addition, remember that React waits for the browser to finish rendering the screen before delaying the useEffect call, thus making extra operations convenient.

Effects that need to be cleared

This section looks at how to implement the willUnmount lifecycle to clean up effect logic when a component is destroyed.

Add logic to the previous demo, click the button to show or hide the coordinate components of the mouse.

Display and remove components

The three files are structured as follows

App.tsx

import React from 'react'

import './App.css'

import MouseContainer from './components/10MouseContainer'

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

Copy the code

MouseContainer.tsx

import React, { useState } from 'react'

import RunEffectsOnlyOnce from './9RunEffectsOnlyOnce'

function MouseContainer() {
  const [display, setDisplay] = useState(true)
  return (
    <div>
      <button onClick={()= >setDisplay(! display)}>Toggle display</button>
      {display && <RunEffectsOnlyOnce />}
    </div>)}export default MouseContainer
Copy the code

MousePos

import React, { useState, useEffect } from 'react'

function RunEffectsOnlyOnce() {

  const [x, setX] = useState(0)
  const [y, setY] = useState(0)

  const logMousePos = (e: MouseEvent) = > {
    setX(e.clientX)
    setY(e.clientY)
  }

  useEffect(() = > {
    console.log('addEventListener')
    document.addEventListener('mousemove', logMousePos)
  }, [])

  return (
    <div>
      Y - {y}, X - {x}
    </div>)}export default RunEffectsOnlyOnce
Copy the code

There is an error warning after execution that the location component is hidden. This is because the child component was not properly unloaded. The Mousemove event is still being monitored and executed. And it can cause memory leaks.

componentWillUnmount

So make sure that all listeners and subscriptions are removed when you uninstall the component. If you are in a Class component, you can do the following

componentWillUnmount() {
  document.removeEventListener('mousemove'.this.logMousePos)
}
Copy the code

But what about useEffect? Please look down

  useEffect(() = > {
    console.log('addEventListener')
    document.addEventListener('mousemove', logMousePos)
    return () = > {
      document.removeEventListener('mousemove', logMousePos)
    }
  }, [])
Copy the code

Add a return anonymous function to the first argument of useEffect, which will be executed when the component is uninstalled, so we will remove the listener here.

If you need some code to clean up when a component uninstalls, put it in the return anonymous function of the first argument to useEffect.

UseEffect bug caused by dependency errors

UseEffect dependency (second argument) error.

Take a counter of +1 per second as an example

Class Component Example

11Counter.tsx

/** * a counter of +1 per second */

import React, { Component } from 'react'

class Counter extends Component {

  state = {
    count: 0
  }

  timer: number | undefined

  tick = () = > {
    this.setState({
      count: this.state.count + 1})}componentDidMount() {
    this.timer = window.setInterval(this.tick, 1000)}componentWillUnmount() {
    clearInterval(this.timer)
  }


  render() {
    return (
      <div>
        <span>{this.state.count}</span>
      </div>)}}export default Counter

Copy the code

App.tsx

import React from 'react'

import './App.css'

import IntervalCounter from './components/11Counter'

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

Copy the code

There is no problem with the execution, and the result is as follows

Hooks sample

IntervalCounterHooks.tsx

import React, { useState, useEffect } from 'react'

function IntervalCouterHooks() {

  const [count, setCount] = useState(0)

  const tick = () = > {
    setCount(count + 1)
  }

  useEffect(() = > {
    const interval = setInterval(tick, 1000)
    return () = > {
      clearInterval(interval)
    }
  }, [])

  return (
    <div>
      {count}
    </div>)}export default IntervalCouterHooks

Copy the code

App.tsx

import React from 'react'

import './App.css'

import IntervalCounter from './components/11Counter'
import IntervalCounterHooks from './components/11IntervalCouterHooks'

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

Copy the code

But the counter does not work properly, and the effect is as follows

Passing an empty dependency array [] means that the hook is run only once when the component is mounted, not re-rendered. The problem is that the value of count does not change during setInterval callbacks. Because when effect is executed, we create a closure and store the value of count in that closure with an initial value of 0. Every second, the callback executes setCount(0 + 1), so count never exceeds 1.

Solution 1: Instead of setting the second argument to useEffect to an empty array, we set it to [count].

Specifying [count] as a dependency list fixes this Bug, but causes the timer to be reset every time a change occurs. In fact, each setInterval (similar to setTimeout) is called once before it is cleared. But that’s not what we want. To solve this problem, we can use the functional update form of setState. It allows us to specify how the state should be changed without referring to the current state, which is solution two, right

Method 2:

will

setCount(count + 1)
Copy the code

Instead of

setCount((preCount) = >  preCount + 1)
Copy the code

UseEffect still uses empty arrays in dependent arrays. Here we set the value of count to be related to the previous value, which also solved the problem. At this point, the setInterval callback is still called once per second, but each time the setCount callback inside the setCount returns the latest count (in the callback case, the variable is named C).

Multiple useEffect

If you have multiple business logic in your code, you can write them in different Useeffects, and you can write multiple Usestates and use them in matching groups to make the business logic clearer.

Fetch Data with Effect Hook

Simple data retrieval

This section describes using useEffect to get data, using axios library examples.

Jsonplaceholder.typicode.com/ site offers sample request, return some json data.

import React, { useState, useEffect } from 'react'

import axios from 'axios'

interface postType {
  userId: number
  id: number
  title: string
  body: string
}

function FetchData() {

  const [posts, setPosts] = useState<postType[]>([])

  useEffect(() = > {
    axios.get('https://jsonplaceholder.typicode.com/posts').then((res) = > {
      const data: postType[] = res.data
      console.log(data)
      setPosts(data)
    }).catch((rej) = > {
      console.log(rej)
    })
  }, [])

  return (
    <div>
      <ul>
        {
          posts.map((item) => (
            <li
              key={item.id}
            >
              {item.title}
            </li>))}</ul>
    </div>)}export default FetchData
Copy the code

Notice how ts is written in useState.

const [posts, setPosts] = useState<postType[]>([])
Copy the code

Note that useEffect is passed an empty array as the second dependency argument, ensuring that useEffect is executed only once.

Enter id to get different data

import React, { useState, useEffect } from 'react'

import axios from 'axios'

interface postType {
  userId: number
  id: number
  title: string
  body: string
}

function FetchData() {
  const [post, setPost] = useState<postType>()
  const [id, setId] = useState('1')

  useEffect(() = > {
    if (id) {
      axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`).then((res) = > {
        const data: postType = res.data
        console.log(data)
        setPost(data)
      }).catch((err) = > {
        console.log(err)
      })
    }
  }, [id])

  return (
    <div>
      <input
        type="text"
        value={id}
        onChange={(e)= > {
          setId(e.target.value)
        }}
      />
      <div>
        {
          post && post.title
        }
      </div>
    </div>)}export default FetchData

Copy the code

Button Click on trigger effect

Listen for the button click to trigger the change and execute the effect method

import React, { useState, useEffect } from 'react'

import axios from 'axios'

interface postType {
  userId: number
  id: number
  title: string
  body: string
}

function FetchData() {
  const [post, setPost] = useState<postType>()
  const [id, setId] = useState('1')
  const [idFromBtnClick, setIdFromBtnClick] = useState('1')

  useEffect(() = > {
    if (idFromBtnClick) {
      axios.get(`https://jsonplaceholder.typicode.com/posts/${idFromBtnClick}`).then((res) = > {
        const data: postType = res.data
        console.log(data)
        setPost(data)
      }).catch((err) = > {
        console.log(err)
      })
    }
  }, [idFromBtnClick])

  return (
    <div>
      <input
        type="text"
        value={id}
        onChange={(e)= > {
          setId(e.target.value)
        }}
      />
      <button
        onClick={()= > {
          setIdFromBtnClick(id)
        }}
      >Fetch Post</button>
      <div>
        {
          post && post.title
        }
      </div>
    </div>)}export default FetchData
Copy the code

summary

This chapter starts from the use of useEffect. Users can solve the problem of repeated code and scattered code, and useEffect can better organize the code.

The use of the useEffect API. The first parameter is an anonymous function that effect executes. The second argument is an array, which is used to conditionally trigger effect by observing the changed props or state, or to pass in an empty array so that effect is executed only once. UseEffect returns an anonymous function that is executed during component destruction to avoid the risk of memory leaks.

Finally, several examples are given to show how the logic to initiate a request for data can be implemented in useEffect. The next chapter covers the useContext API.