Reactでfetchを使用した際にdev環境だと2回リクエストしてしまう問題の解決方法

Reactではfetchを使用時に2回リクエストする

Reactではfetchを使用時にdev(開発)環境だとStrictModeが使用されている場合は2回リクエストが発生します。

App.tsx
import { useEffect } from 'react'

function App() {
  useEffect(() => {
    fetch('https://dummyjson.com/test')
      .then(res => res.json())
      .then(data => {
        console.log(data)
        // {status: 'ok', method: 'GET'}
        // {status: 'ok', method: 'GET'}
      })
  }, [])

  return <h1>fetch sample</h1>
}

export default App

StrictModeが使用されている場合は2回リクエストが発生するのは正常な動作ですが、コードによっては1回のリクエストで済ませたい場合もあります。

そんなときはuseEffect内でreturn () => {} (クリーンアップ) を使用してフラグを追加して以下のコードにすればリクエストが1回で済みます。

App.tsx
import { useEffect } from 'react'

function App() {
  useEffect(() => {
    let isFetch = false

    fetch('https://dummyjson.com/test')
      .then(res => res.json())
      .then(data => {
        if (!isFetch) {
          console.log(data)
          // {status: 'ok', method: 'GET'}
        }
      })
    return () => { isFetch = true }
  }, [])

  return <h1>fetch sample</h1>
}

export default App

クリーンアップはconsole.logを1回だけ出したいケースでも使えます。

App.tsx
import { useEffect } from 'react'

function App() {
  useEffect(() => {
    return () => console.log('Hello')
  }, [])

  return <h1>fetch sample</h1>
}

export default App

useRefで初回のみ実行する

useRefを使用して初回レンダリングのフラグを設定することでも初回レンダリング時のみ実行できます。

一般的にはこちらのやり方のほうが主流です。

App.tsx
const isInitialRender = useRef(true)

useEffect(() => {
  if (isInitialRender.current) {
    isInitialRender.current = false
    
    // 初回レンダリング時のみ実行する処理
  }
}, [])

ReactのStrictModeでコンポーネントが2回レンダリングされるのは副作用が正しく動作されているか確認するためなので、特別な理由がなければ使わないようにしてください。