React, useEffectの挙動

ReactuseEffectの挙動にハマってたので、メモを残しておく。

開発環境

以下のReactコードの雛形は、create-react-appで作成。別途Node.js+Expressを使ったサーバーを別のポートで起動して、proxy設定でリクエストを転送しています。

関数コンポーネントと、useStateuseEffectを使って、起動時に非同期でサーバにリクエストを送信して、取得した値をステートに保持して、画面を更新する内容になっています。

import React, { useState, useEffect } from 'react'
import './App.css'

function App() {
  const [users, setUsers] = useState([])
  const [val, setVal] = useState(0)
  const [val2, setVal2] = useState(0)
  useEffect( () => {
    console.log("--useEffect--")
    const func = async()=>{
      let response = await fetch('/users')
      let data = await response.json()
      console.log(data)
      setUsers(data)
    }
    func()
  },[val])

  return (
    <>
      <div className="App">
        <header className="App-header">
          <p>You clicked val={val} times</p>
          <p>You clicked val2={val2} times</p>
          {
            users.map(user =>
              <div key={user.id}>{user.username} </div>
            )
          }
          <button onClick={() => setVal(val + 1)}>
            val + 1
          </button>
          <button onClick={() => setVal2(val2 + 1)}>
            val2 + 1
          </button>
        </header>
      </div>
      
    </>
  )
}

export default App

サーバーは/usersにアクセスすると以下のJsonがレスポンスされる前提です。

[
  {
    id: 1,
    username: "person1"
  }, {
    id: 2,
    username: "person2"
  }
]

ポイント1 : useEffect内での非同期通信

fetchを使っています。awaitを使ってレスポンスの取得やjson変換は同期処理になっているが、全体としては非同期処理です。useEffectのコールバック関数にasyncを指定すると怒られるので、以下の様に内部で関数を定義して実行しないとダメでした。

  useEffect( () => {
    const func = async()=>{
      let response = await fetch('/users')
      let data = await response.json()
      setUsers(data)
    }
    func()
  },[])

ポイント2 : useEffectの第2引数を空でも良いので指定すること

あと、内部でsetUsers()をコールして値を保持しますが、useEffectの第2引数で配列を指定しないと無限ループします。最低限空の配列をしています。

この第2引数の配列に値を設定すると、設定した値が変更になったときにuseEffectが実行される。[]だとマウントとアンマウント時のみ、[val]だとプラスでvalが変更になったときにコールされます。

1個目のボタンをクリックするとuseEffectがコールされて、2個目のボタンだとコールされないです。

起動後、2個目のボタンを数回クリック。useEffectは1回だけコールされている。

react-useeffect01

その後、1個目のボタンを数回クリック。クリックした回数だけuseEffectがコールされている。

react-useeffect02