⚡️

fetcherの中でsetStateを呼んだりしてないよね?

2023/09/13に公開

自戒のための記事です。

TL;DR

  • React(というか fetch)の副作用について
  • 非同期関数を useEffect 内で呼ぶ場合の注意点について

fetcher の中で setState を呼んではいけない

このようなコードを日常的に書いたりしていますか??

//とあるコンポーネント内部にて
const [data, setData] = useState()
const fetchData = () => {
    const response = await fetch("/hogehoge").catch((e) => { console.error(e);});
    if (response !== undefined && response.ok) {
        const data = await response.json();
        if (
            isTypeGuradOfData(data) &&
            data.statusCode === 200
        ) {
            setData(data);
        }
    }
    setData({});
}

//初期値設定用
useEffect(() => {
    fetchData();
}, []);

この場合の fetchData という関数は、関数内部で外部の state の値を変更してしまっているので、純粋関数ではなく副作用のある関数になってしまっています。

純粋関数と副作用については以下の記事を参照してください。

https://blog-mayo.com/2022/09/881/#toc4

なぜか?

setState は外部の値を書き換える副作用を引き起こすからです。

その他にも副作用はいろいろありますが、副作用は極力関数から切り出して、それを書くべき場所=useEffectで利用するべきです。

改善

先ほどのコードをあるべき形に近づけます。

//とあるコンポーネント内部にて
const [data, setData] = useState()
const fetchData = () => {
    const response = await fetch("/hogehoge").catch((e) => { console.error(e);});
    if (response !== undefined && response.ok) {
        const data = await response.json();
        if (
            isTypeGuradOfData(data) &&
            data.statusCode === 200
        ) {
            return data;
        }
    }
    return {};
}

//初期値設定用
useEffect(() => {
    //エラーになる
    const data = await fetchData();
    setData(data)
}, []);

しかし、これでもエラーとなります。

理由は、コールバックに非同期関数を渡した場合、そのコールバックの戻り値が useEffect の本来の引数として想定されていない Promise 型になってしまうからです。

では、useEffect内部で非同期関数を使う方法について見ていきましょう。

useEffect内部で非同期関数を使う方法

方法は 2 つあります。

useEffect内部で関数を定義し、その内部で非同期関数を呼ぶ

//初期値設定用
useEffect(() => {
    const getData = async () => {
        const data = await fetchData();
        setData(data);
    }
    getData();
}, []);

Promise.then を使って非同期関数を呼ぶ

//初期値設定用
useEffect(() => {
    fetchData().then((data) => {
        setData(data)
    })
}, []);

ただし、こちらの方法の場合はfetchsetDataが非同期に実行されてしまうため、最初のfetchで取得した値を使ってさらにfetchするような場合には.thenをどんどんつなげないとダメになります。

なので、そのような場合には最初の方法の方が意味のわかるコードになりそうです。

おわりに

副作用のない純粋関数を作るよう、常日頃から意識しておきたいですね。

メンバー募集中!

サーバーサイド Kotlin コミュニティを作りました!

Kotlin ユーザーはぜひご参加ください!!

https://serverside-kt.connpass.com/

また関西在住のソフトウェア開発者を中心に、関西エンジニアコミュニティを一緒に盛り上げてくださる方を募集しています。

よろしければ Conpass からメンバー登録よろしくお願いいたします。

https://blessingsoftware.connpass.com/

Discussion