
ReactでuseEffectで初期化の処理をしてはいけない
React初心者によくある誤解のひとつに、「初期化処理はuseEffectで行う」というものがあります。
確かに、useEffectに初期処理を書くと「コンポーネントのマウント時に一度だけ実行される」ため、一見正しそうに見えます。
しかし、実際の開発ではuseEffectで初期化処理を書くことは避けるべきです。
useEffectで初期化処理を書いてしまう例
TSX
import { useEffect, useState } from 'react'
export default function App() {
const [user, setUser] = useState<{ name: string } | null>(null)
useEffect(() => {
setUser({ name: 'Taro' })
}, [])
return <div>{user ? user.name : 'Loading...'}</div>
}
useEffectで初期化してはいけない理由
以下のコードで最初のレンダリングから正しい状態を保持できます。
TSX
const [user] = useState({ name: 'Taro' })
よって、useEffect を使う必要はありません。
fetchでの外部データ取得について
fetchで外部データを取得する際にも、useStateが以下のように使われているケースをよく見かけます。
App.tsx
import { useEffect, useState } from 'react'
import './App.css'
function App() {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
const fetchData = async () => {
setLoading(true)
try {
const response = await fetch('https://dummyjson.com/users/1?select=firstName,age')
const result = await response.json()
setData(result)
}
catch (error) {
console.error(error)
}
finally {
setLoading(false)
}
}
useEffect(() => {
fetchData()
}, [])
return (
<div>
<button onClick={fetchData} disabled={loading}>
{data ? '再読み込み' : 'データを取得'}
</button>
{loading && <p>Loading...</p>}
{data && !loading && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
)
}
export default App
しかし、useEffectで外部データを取得すると、開発環境での起動中(npm run dev)はReactでは2回レンダリングされるため、2回データを取得してしまいます。
ボタンを押したときのレスポンスは1回なのに、初回読み込み時のレスポンスは2回というのはおかしいです。
そのため、この場合はuseRefで外部データ取得のフラグを管理して、1回だけデータを取得したほうが良いです。
App.tsx
import { useState, useRef } from 'react'
import './App.css'
function App() {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
const isInitialized = useRef(false)
const fetchData = async () => {
setLoading(true)
try {
const response = await fetch('https://dummyjson.com/users/1?select=firstName,age')
const result = await response.json()
setData(result)
}
catch (error) {
console.error(error)
}
finally {
setLoading(false)
}
}
if (!isInitialized.current) {
isInitialized.current = true
fetchData()
}
return (
<div>
<button onClick={fetchData} disabled={loading}>
{data ? '再読み込み' : 'データを取得'}
</button>
{loading && <p>Loading...</p>}
{data && !loading && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
)
}
export default App
useRefでfetchでの外部データ取得を1回だけにするサンプル