Tự học ReactJS: Sử dụng useEffect

Tự học ReactJS: Sử dụng useEffect

React.useEffect là một hook được build sẵn trong React. Nó cho phép chạy một số side-effect sau khi một component đã render. Nó nhận vào một callback function và sẽ được React gọi sau khi DOM được cập nhật:

React.useEffect(() => {
  // code side-effect của bạn ở đây
  // đây là nơi lý tưởng để bạn gọi API hay tương tác với browser API
})

Khởi tạo initial state từ localStorage

Trong bài tập này, chúng ta sẽ cập nhật <Greeting /> component một chút để nó có thể lấy giá trị ban đầu từ localStorage và giữ localStorage lưu đúng giá trị của name input.

import * as React from 'react'

function Greeting({initialName = ''}) {
  // 🐨 khởi tạo state từ localStorage
  // 💰 window.localStorage.getItem('name') || initialName
  const [name, setName] = React.useState(initialName)

  // 🐨 Bạn có thể sử dụng `React.useEffect`.
  // để lưu state name vào localStorage.
  // 💰 window.localStorage.setItem('name', name)

  function handleChange(event) {
    setName(event.target.value)
  }
  return (
    <div>
      <form>
        <label htmlFor="name">Name: </label>
        <input value={name} onChange={handleChange} id="name" />
      </form>
      {name ? <strong>Hello {name}</strong> : 'Please type your name'}
    </div>
  )
}

function App() {
  return <Greeting />
}

export default App

Đáp án:

import * as React from 'react'

function Greeting({initialName = ''}) {
  const [name, setName] = React.useState(
    window.localStorage.getItem('name') || initialName,
  )

  React.useEffect(() => {
    window.localStorage.setItem('name', name)
  })

  function handleChange(event) {
    setName(event.target.value)
  }

  return (
    <div>
      <form>
        <label htmlFor="name">Name: </label>
        <input value={name} onChange={handleChange} id="name" />
      </form>
      {name ? <strong>Hello {name}</strong> : 'Please type your name'}
    </div>
  )
}

function App() {
  return <Greeting />
}

export default App

Khởi tạo lazy state

Hiện tại, mỗi khi component function chạy, nó sẽ đọc giá trị từ localStorage. Điều này có thể gây vấn đề về hiệu suất(performace) – đọc state từ localStorage hay từ một third party nào đó có thể làm chậm component của bạn. Nếu như chúng ta chỉ cần khởi tạo state khi component render một lần ban đầu thì có cách nào nhanh hơn không?

Để tối ưu việc khởi tạo state trong tình huống này, React cung cấp cho chúng ta một tùy chọn là có thể truyền vào một function như một callback khi bạn khởi tạo state từ React.useState.

React.useState(someExpensiveComputation()) được tối ưu bằng: React.useState(() => someExpensiveComputation())

Và hàm someExpensiveComputation sẽ chỉ được gọi mỗi khi cần.

Bạn hãy viết lại React.useState theo kiểu lazy như trên để khởi tạo state từ localStorage.

 const [name, setName] = React.useState(
    () => window.localStorage.getItem('name') || initialName,
  )

Các dependencies của useEffect

Hàm(callback) chúng ta truyền vào React.useEffect được gọi sau mỗi lần render của component(bao gồm cả re-render). Đó là những gì chúng ta muốn bởi vì chúng ta cần lưu name vào localStorage mỗi khi nó thay đổi. Nhưng vấn đề ở đây là một component có thể re-render rất nhiều lần (ví dụ như khi component cha re-render)

Thực sự chúng ta chỉ muốn gọi hàm callback của useEffect mỗi khi name thay đổi. Nó không cần chạy lại mỗi khi component render. May mắn thay, React.useEffect cho phép chúng ta truyền vào một tham số thứ hai gọi là “dependency array“. Điều này giúp chúng ta chỉ gọi useEffect mỗi khi giá trị của dependency array thay đổi. Chúng ta có thể tránh được việc useEffect chạy quá nhiều lần không cần thiết làm ảnh hưởng đến performace của component.

Bạn hãy thêm dependency array cho useEffect ở trên để tránh callback gọi quá nhiều lần.

 React.useEffect(() => {
    window.localStorage.setItem('name', name)
  }, [name])

Tạo custom hook

Một trong những điều hay ho với hooks là bạn có thể viết lại logic trong component ở một custom hook nào đó để sử dụng lại cho các component khác. Điều mà ở Class Component bạn khó có thể làm được. Bạn có thể tạo một file custom hook dùng chung và chia sẻ logic cho toàn app. Những hooks này được gọi là "custom hooks"

Bạn hãy tạo một custom hook tên là useLocalStorage để tái sử dụng logic này.

Đáp án:

import * as React from 'react'

function useLocalStorageState(key, defaultValue = '') {
  const [state, setState] = React.useState(
    () => window.localStorage.getItem(key) || defaultValue,
  )

  React.useEffect(() => {
    window.localStorage.setItem(key, state)
  }, [key, state])

  return [state, setState]
}

function Greeting({initialName = ''}) {
  const [name, setName] = useLocalStorageState('name', initialName)

  function handleChange(event) {
    setName(event.target.value)
  }

  return (
    <div>
      <form>
        <label htmlFor="name">Name: </label>
        <input value={name} onChange={handleChange} id="name" />
      </form>
      {name ? <strong>Hello {name}</strong> : 'Please type your name'}
    </div>
  )
}

function App() {
  return <Greeting />
}

export default App

Nâng cấp localStorage hook

Hãy viết lại custom hook của bạn useLocalStorageđể nó linh hoạt và hỗ trợ nhiều dạng data hơn như object ( nhớ rằng, bạn phải chuyển đổi objects thành string … và sử dụng JSON.stringifyJSON.parse).

Đáp án:

function useLocalStorageState(
  key,
  defaultValue = '',
  {serialize = JSON.stringify, deserialize = JSON.parse} = {},
) {
  const [state, setState] = React.useState(() => {
    const valueInLocalStorage = window.localStorage.getItem(key)
    if (valueInLocalStorage) {
      try {
        return deserialize(valueInLocalStorage)
      } catch (error) {
        window.localStorage.removeItem(key)
      }
    }
    return typeof defaultValue === 'function' ? defaultValue() : defaultValue
  })

  const prevKeyRef = React.useRef(key)

  React.useEffect(() => {
    const prevKey = prevKeyRef.current
    if (prevKey !== key) {
      window.localStorage.removeItem(prevKey)
    }
    prevKeyRef.current = key
    window.localStorage.setItem(key, serialize(state))
  }, [key, state, serialize])

  return [state, setState]
}

function Greeting({initialName = ''}) {
  const [name, setName] = useLocalStorageState('name', initialName)

  function handleChange(event) {
    setName(event.target.value)
  }

  return (
    <div>
      <form>
        <label htmlFor="name">Name: </label>
        <input value={name} onChange={handleChange} id="name" />
      </form>
      {name ? <strong>Hello {name}</strong> : 'Please type your name'}
    </div>
  )
}

function App() {
  return <Greeting />
}

export default App

thaunguyen.com via Epic React by Kent C.Dodds

Leave a Comment