【React基礎】useEffect内でasyncを直接書いてはいけない理由
質問
突然ですが、以下のコードの何がいけないでしょうか?
import { useEffect, useState } from "react"
import { supabase } from "./supabase"
export const Arunba = () => {
const [userName, setUserName] = useState(null)
useEffect(async () => {
const userName = await supabase.auth.getUser()
setUserName(userName)
}, [])
return (
<h1>Hello Im ${userName}!!</h1>
)
}
2秒以内に分かった人は、React強者(個人的主観)
答え:useEffectにasyncを直接つけてはいけない
理由1:useEffectの戻り値は「クリーンアップ関数」である
useEffect
は、副作用の登録と同時に「クリーンアップ関数(後始末関数)」を返すことができます。
Reactはこの戻り値をPromiseではなく関数として扱う前提になっています。
useEffect(() => {
// effectの実行
return () => {
// cleanup処理
}
}, [])
理由2:asyncを付けるとPromiseを返してしまう
async
をつけた関数は、明示的にreturn
を書かなくても必ずPromiseを返します。
async function test() {
console.log("hello")
}
console.log(test()) // => Promise<void>
したがって、以下のように書くと:
useEffect(async () => {
await someAsyncTask()
}, [])
内部的には「Promiseを返す関数」を渡したことになり、
Reactは「クリーンアップ関数」と「Promise」とを区別できなくなります。
React公式ドキュメントの記述
React公式でも以下のように明記されています。
The effect function should not return anything besides a cleanup function.
In particular, it should not return a Promise.
(副作用関数はクリーンアップ関数以外を返してはいけません。特にPromiseを返してはいけません。)
正しい書き方
Promiseを返す処理を内部のasync関数に切り出して呼び出すことで解決します。
import { useEffect, useState } from "react"
import { supabase } from "./supabase"
export const Arunba = () => {
const [userName, setUserName] = useState("")
useEffect(() => {
const fetchUser = async () => {
const { data, error } = await supabase.auth.getUser()
if (error) {
console.error(error)
return
}
setUserName(data.user?.email || "Guest")
}
fetchUser()
}, [])
return <h1>Hello I'm {userName}!!</h1>
}
このようにすれば、
useEffect
の第一引数は「同期関数」のまま、内部で非同期処理を安全に実行できます。
代替案:Promiseチェーンを使う書き方
async/await
を使わずに、Promiseチェーンで書く方法もあります。
useEffect(() => {
supabase.auth.getUser().then(({ data }) => {
setUserName(data.user?.email || "Guest")
})
}, [])
これでも、戻り値はPromiseではなくundefined
となるため問題ありません。
まとめ
観点 | 説明 |
---|---|
useEffectの戻り値 | クリーンアップ関数のみ許可される |
asyncをつけると | Promiseを返してしまうためReactが混乱する |
解決方法 | 内部にasync関数を定義して呼ぶ or thenで書く |