useEffectの非同期関数に対するReactフックの警告:useEffect関数はクリーンアップ関数を返すか、何も返さない必要があります


115

私は以下のようなuseEffectの例を試していました:

useEffect(async () => {
    try {
        const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
        const json = await response.json();
        setPosts(json.data.children.map(it => it.data));
    } catch (e) {
        console.error(e);
    }
}, []);

コンソールにこの警告が表示されます。しかし、私が考える非同期呼び出しでは、クリーンアップはオプションです。なぜこの警告が出るのかわかりません。サンドボックスをリンクして例を示します。https://codesandbox.io/s/24rj871r0p ここに画像の説明を入力してください

回答:


161

Dan Abramov(Reactコアメンテナの1人)の回答をここで確認することをお勧めします。

必要以上に複雑になっていると思います。

function Example() {
  const [data, dataSet] = useState<any>(null)

  useEffect(() => {
    async function fetchMyAPI() {
      let response = await fetch('api/data')
      response = await response.json()
      dataSet(response)
    }

    fetchMyAPI()
  }, [])

  return <div>{JSON.stringify(data)}</div>
}

長期的には、このパターンは競合状態を助長するため、推奨されません。など-通話の開始と終了の間に何かが発生する可能性があり、新しい小道具を手に入れることができます。代わりに、データのフェッチにはSuspenseをお勧めします。

const response = MyAPIResource.read();

影響なし。しかし、その間、非同期のものを別の関数に移動して呼び出すことができます。

実験的なサスペンスについて詳しくは、こちらをご覧ください


eslintで外部の関数を使用したい場合。

 function OutsideUsageExample() {
  const [data, dataSet] = useState<any>(null)

  const fetchMyAPI = useCallback(async () => {
    let response = await fetch('api/data')
    response = await response.json()
    dataSet(response)
  }, [])

  useEffect(() => {
    fetchMyAPI()
  }, [fetchMyAPI])

  return (
    <div>
      <div>data: {JSON.stringify(data)}</div>
      <div>
        <button onClick={fetchMyAPI}>manual fetch</button>
      </div>
    </div>
  )
}

useCallback useCallbackを使用します。サンドボックス

import React, { useState, useEffect, useCallback } from "react";

export default function App() {
  const [counter, setCounter] = useState(1);

  // if counter is changed, than fn will be updated with new counter value
  const fn = useCallback(() => {
    setCounter(counter + 1);
  }, [counter]);

  // if counter is changed, than fn will not be updated and counter will be always 1 inside fn
  /*const fnBad = useCallback(() => {
      setCounter(counter + 1);
    }, []);*/

  // if fn or counter is changed, than useEffect will rerun
  useEffect(() => {
    if (!(counter % 2)) return; // this will stop the loop if counter is not even

    fn();
  }, [fn, counter]);

  // this will be infinite loop because fn is always changing with new counter value
  /*useEffect(() => {
    fn();
  }, [fn]);*/

  return (
    <div>
      <div>Counter is {counter}</div>
      <button onClick={fn}>add +1 count</button>
    </div>
  );
}

:コンポーネントがそうのようにアンマウントされている場合は、チェックすることにより、競合状態の問題を解決することができ useEffect(() => { let unmounted = false promise.then(res => { if (!unmounted) { setState(...) } }) return () => { unmounted = true } }, [])
リチャード・

1
use-async-effectという名前のパッケージを使用することもできます。このパッケージを使用すると、async await構文を使用できます。
KittyCat

自己呼び出し関数を使用すると、useEffect関数の定義に非同期リークが発生しないか、useEffectのラッパーとして非同期呼び出しをトリガーする関数のカスタム実装が今のところ最善の策です。提案されたuse-async-effectのような新しいパッケージを含めることができますが、これは解決する簡単な問題だと思います。
Thulani Chivandikwa

1
ねえ、それは大丈夫だと私がほとんどの場合行うこと。しかし依存関係としてeslint作るように私に頼むfetchMyAPI()useEffect
プラカシュレディポトラパドゥ

51

次のような非同期関数を使用する場合

async () => {
    try {
        const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
        const json = await response.json();
        setPosts(json.data.children.map(it => it.data));
    } catch (e) {
        console.error(e);
    }
}

それは約束を返し、 useEffectコールバック関数が約束を返すように期待していない、むしろそれは何も返されない場合、または関数が返されることを期待しています。

警告の回避策として、自己起動非同期関数を使用できます。

useEffect(() => {
    (async function() {
        try {
            const response = await fetch(
                `https://www.reddit.com/r/${subreddit}.json`
            );
            const json = await response.json();
            setPosts(json.data.children.map(it => it.data));
        } catch (e) {
            console.error(e);
        }
    })();
}, []);

または、よりクリーンにするために、関数を定義して呼び出すことができます

useEffect(() => {
    async function fetchData() {
        try {
            const response = await fetch(
                `https://www.reddit.com/r/${subreddit}.json`
            );
            const json = await response.json();
            setPosts(json.data.children.map(it => it.data));
        } catch (e) {
            console.error(e);
        }
    };
    fetchData();
}, []);

2番目のソリューションは、読み取りを容易にし、新しい要求が発生した場合に前の要求をキャンセルするか、最新の要求応答を状態に保存するコードを作成するのに役立ちます

作業コードサンドボックス


これを簡単にするためのパッケージが作成されました。あなたはここでそれを見つけることができます。
KittyCat 2018年

1
しかし、eslintはそれを容認しません
Muhaimin CS

1
クリーンアップ/ didmountコールバックを実行する方法はありません
デビッドリアテ

1
@ShubhamKhatriを使用するuseEffectと、イベントのサブスクライブ解除などのクリーンアップを行う関数を返すことができます。非同期関数を使用するuseEffectと、結果を待たないため何も返すことができません
David Rearte '10

2
私は非同期機能にクリーンアップ機能を配置できると言っていますか?試しましたが、クリーンアップ関数が呼び出されません。少し例を挙げていただけますか?
デビッドリアテ

32

Reactがより良い方法を提供するまで、あなたはヘルパーを作成することができますuseEffectAsync.js

import { useEffect } from 'react';


export default function useEffectAsync(effect, inputs) {
    useEffect(() => {
        effect();
    }, inputs);
}

これで非同期関数を渡すことができます:

useEffectAsync(async () => {
    const items = await fetchSomeItems();
    console.log(items);
}, []);

8
ReactがuseEffectで非同期関数を自動的に許可しない理由は、ケースの大部分で、いくつかのクリーンアップが必要なためです。機能useAsyncEffectあなたが書いたように、それは、彼らはそれが適切な時間に実行されるだろう彼らの非同期影響からクリーンアップ機能を思考に簡単に誤解誰かを返すことができます。これはメモリリークやバグの悪化につながる可能性があるため、Reactのライフサイクルとやり取りする非同期関数の「継ぎ目」をより見やすくするためにコードをリファクタリングすることを人々に奨励することを選択しました。
ソフィーアルパート

8

私はこの質問を読みましたが、useEffectを実装する最良の方法は回答に記載されていません。ネットワークコールがあり、応答があったら何かを実行したいとします。簡単にするために、ネットワーク応答を状態変数に格納します。ネットワーク応答でストアを更新するために、アクション/レデューサーを使用したい場合があります。

const [data, setData] = useState(null);

/* This would be called on initial page load */
useEffect(()=>{
    fetch(`https://www.reddit.com/r/${subreddit}.json`)
    .then(data => {
        setData(data);
    })
    .catch(err => {
        /* perform error handling if desired */
    });
}, [])

/* This would be called when store/state data is updated */
useEffect(()=>{
    if (data) {
        setPosts(data.children.map(it => {
            /* do what you want */
        }));
    }
}, [data]);

リファレンス=> https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects


1

試す

const MyFunctionnalComponent: React.FC = props => {
  useEffect(() => {
    // Using an IIFE
    (async function anyNameFunction() {
      await loadContent();
    })();
  }, []);
  return <div></div>;
};


1

他のリーダーの場合、エラーは、非同期関数をラップするブラケットがないという事実に起因する可能性があります。

非同期関数initDataを検討する

  async function initData() {
  }

このコードはあなたのエラーにつながります:

  useEffect(() => initData(), []);

しかし、これはしません:

  useEffect(() => { initData(); }, []);

(initData()の前後の括弧に注意してください


1
素晴らしい、男!私は佐賀を使用していますが、唯一のオブジェクトを返すアクションクリエーターを呼び出したときにそのエラーが表示されました。コールバック関数のuseEffectがこの動作をなめないようです。回答ありがとうございます。
Gorr1995

1
なぜこれが本当なのか疑問に思っている人のために...中括弧がないと、initData()の戻り値は、矢印関数によって暗黙的に返されます。中括弧を使用すると、暗黙的に何も返されないため、エラーは発生しません。
Marnix.hoh

1

ここではvoid演算子を使用できます。
の代わりに:

React.useEffect(() => {
    async function fetchData() {
    }
    fetchData();
}, []);

または

React.useEffect(() => {
    (async function fetchData() {
    })()
}, []);

あなたは書くことができます:

React.useEffect(() => {
    void async function fetchData() {
    }();
}, []);

それは少しきれいできれいです。


非同期の影響によりメモリリークが発生する可能性があるため、コンポーネントのマウント解除をクリーンアップすることが重要です。フェッチの場合、これは次のようになります。

function App() {
    const [ data, setData ] = React.useState([]);

    React.useEffect(() => {
        const abortController = new AbortController();
        void async function fetchData() {
            try {
                const url = 'https://jsonplaceholder.typicode.com/todos/1';
                const response = await fetch(url, { signal: abortController.signal });
                setData(await response.json());
            } catch (error) {
                console.log('error', error);
            }
        }();
        return () => {
            abortController.abort(); // cancel pending fetch request on component unmount
        };
    }, []);

    return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.