0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【当然】useEffectのクリーンアップ関数にはPromiseを返すものはダメだよ

Posted at

【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を返してはいけません。)

参照:React Docs – useEffect

image.png


正しい書き方

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で書く

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?