Mỗi effect ở trong useEffect phải là một quá trình đồng bộ hóa độc lập.

Giả sử ta có component ShippingForm có chứa một drop down cho phép người dùng chọn một quốc gia:

import { useState, useEffect } from "react"
 
function ShippingForm({ country }) {
  const [cities, setCities] = useState(null)
 
  useEffect(() => {
    let ignore = false
    fetch(`/api/cities?country=${country}`)
      .then((response) => response.json())
      .then((json) => {
        if (!ignore) {
          setCities(json)
        }
      })
    return () => {
      ignore = true
    }
  }, [country]) // ✅ All dependencies declared
 
  // ...
}

Khi người dùng thay đổi country thì ta sẽ gọi API để lấy danh sách các thành phố của quốc gia đó. Cleanup function trong đoạn code trên có nhiệm vụ giải quyết vấn đề race condition1.

Giả sử ta thêm vào một drop down cho phép người dùng chọn một thành phố và sau đó lấy ra danh sách các khu vực của thành phố đó. Xử lý tác vụ này ở trong cùng một effect ở trên như sau:

import { useState, useEffect } from "react"
 
function ShippingForm({ country }) {
  const [cities, setCities] = useState(null)
  const [city, setCity] = useState(null)
  const [areas, setAreas] = useState(null)
 
  useEffect(() => {
    let ignore = false
 
    fetch(`/api/cities?country=${country}`)
      .then((response) => response.json())
      .then((json) => {
        if (!ignore) {
          setCities(json)
        }
      })
 
    // 🔴 Avoid: A single Effect synchronizes two independent processes
    if (city) {
      fetch(`/api/areas?city=${city}`)
        .then((response) => response.json())
        .then((json) => {
          if (!ignore) {
            setAreas(json)
          }
        })
    }
 
    return () => {
      ignore = true
    }
  }, [country, city]) // ✅ All dependencies declared
 
  // ...
}

Lúc này, khi người dùng thay đổi thành phố thì cả danh sách các thành phố và danh sách các khu vực đều được tải lại. Điều này sẽ làm giảm hiệu năng, đặc biệt là khi các danh sách có kích thước lớn.

Để có thể giải quyết vấn đề này, ta nên tách ra thành hai effect riêng biệt, mỗi effect có nhiệm vụ đồng bộ hóa của riêng nó:

import { useState, useEffect } from "react"
 
function ShippingForm({ country }) {
  const [cities, setCities] = useState(null)
  useEffect(() => {
    let ignore = false
    fetch(`/api/cities?country=${country}`)
      .then((response) => response.json())
      .then((json) => {
        if (!ignore) {
          setCities(json)
        }
      })
    return () => {
      ignore = true
    }
  }, [country]) // ✅ All dependencies declared
 
  const [city, setCity] = useState(null)
  const [areas, setAreas] = useState(null)
  useEffect(() => {
    if (city) {
      let ignore = false
      fetch(`/api/areas?city=${city}`)
        .then((response) => response.json())
        .then((json) => {
          if (!ignore) {
            setAreas(json)
          }
        })
      return () => {
        ignore = true
      }
    }
  }, [city]) // ✅ All dependencies declared
 
  // ...
}

Bằng cách này, effect đầu tiên sẽ chỉ chạy lại nếu state country thay đổi và effect thứ hai sẽ chỉ chạy lại nếu state city thay đổi.

Resources

Footnotes

  1. xem thêm Fetching Data.