Peringatan React Hook untuk fungsi asinkron dalam useEffect: fungsi useEffect harus mengembalikan fungsi pembersihan atau tidak sama sekali

120

Saya mencoba contoh useEffect seperti di bawah ini:

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);
    }
}, []);

dan saya mendapatkan peringatan ini di konsol saya. Tapi pembersihan adalah opsional untuk panggilan async menurut saya. Saya tidak yakin mengapa saya mendapatkan peringatan ini. Menautkan sandbox misalnya. https://codesandbox.io/s/24rj871r0p masukkan deskripsi gambar di sini

RedPandaz
sumber

Jawaban:

167

Saya menyarankan untuk melihat jawaban Dan Abramov (salah satu pengelola inti React) di sini :

Saya pikir Anda membuatnya lebih rumit dari yang seharusnya.

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>
}

Jangka panjang kami akan mencegah pola ini karena mendorong kondisi balapan. Seperti - apa pun bisa terjadi antara panggilan Anda dimulai dan diakhiri, dan Anda bisa mendapatkan properti baru. Sebagai gantinya, kami akan merekomendasikan Suspense untuk pengambilan data yang lebih mirip

const response = MyAPIResource.read();

dan tidak ada efek. Namun sementara itu Anda dapat memindahkan hal-hal asinkron ke fungsi terpisah dan memanggilnya.

Anda dapat membaca lebih lanjut tentang ketegangan eksperimental di sini .


Jika Anda ingin menggunakan fungsi luar dengan 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>
  )
}

Dengan useCallback useCallback . Sandbox .

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>
  );
}
ZiiMakc
sumber
Anda dapat menyelesaikan masalah kondisi balapan dengan memeriksa apakah komponen dilepas seperti ini: useEffect(() => { let unmounted = false promise.then(res => { if (!unmounted) { setState(...) } }) return () => { unmounted = true } }, [])
Richard
1
Anda juga dapat menggunakan paket bernama use-async-effect . Paket ini memungkinkan Anda menggunakan sintaks async await.
KittyCat
Menggunakan fungsi pemanggilan mandiri tidak membiarkan asinkron bocor ke definisi fungsi useEffect atau implementasi khusus dari fungsi yang memicu panggilan asinkron sebagai pembungkus di sekitar useEffect adalah taruhan terbaik untuk saat ini. Meskipun Anda dapat menyertakan paket baru seperti efek-asinkron yang disarankan, saya pikir ini adalah masalah sederhana untuk dipecahkan.
Thulani Chivandikwa
1
hei itu bagus dan apa yang sering saya lakukan. tetapi eslintmeminta saya untuk menjadikan fetchMyAPI()sebagai ketergantunganuseEffect
Prakash Reddy Potlapadu
51

Saat Anda menggunakan fungsi async seperti

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);
    }
}

ia mengembalikan sebuah promise dan useEffecttidak mengharapkan fungsi callback mengembalikan Promise, melainkan ia mengharapkan tidak ada yang dikembalikan atau fungsi dikembalikan.

Sebagai solusi untuk peringatan tersebut, Anda dapat menggunakan fungsi asinkron yang memanggil sendiri.

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);
        }
    })();
}, []);

atau untuk membuatnya lebih bersih Anda dapat mendefinisikan sebuah fungsi dan kemudian memanggilnya

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();
}, []);

solusi kedua akan membuatnya lebih mudah dibaca dan akan membantu Anda menulis kode untuk membatalkan permintaan sebelumnya jika permintaan baru diaktifkan atau menyimpan respons permintaan terbaru dalam status

Kode kerja dan kotak

Shubham Khatri
sumber
Sebuah paket untuk mempermudah ini telah dibuat. Anda bisa menemukannya di sini .
KittyCat
1
tapi eslint tidak akan mentolerir dengan itu
Muhaimin CS
1
tidak ada cara untuk menjalankan panggilan balik pembersihan / didmount
David Rearte
1
@ShubhamKhatri saat Anda menggunakan, useEffectAnda dapat mengembalikan fungsi untuk melakukan pembersihan seperti berhenti berlangganan acara. Ketika Anda menggunakan fungsi async, Anda tidak dapat mengembalikan apa pun karena useEffecttidak akan menunggu hasilnya
David Rearte
2
apakah Anda mengatakan saya dapat menempatkan fungsi pembersihan di async? saya mencoba tetapi fungsi pembersihan saya tidak pernah dipanggil. Bisakah Anda membuat sedikit contoh?
David Rearte
32

Sampai Bereaksi menyediakan cara yang lebih baik, Anda dapat membuat pembantu, useEffectAsync.js:

import { useEffect } from 'react';


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

Sekarang Anda dapat meneruskan fungsi async:

useEffectAsync(async () => {
    const items = await fetchSomeItems();
    console.log(items);
}, []);
Ed I
sumber
9
Alasan React tidak secara otomatis mengizinkan fungsi asinkron di useEffect adalah karena dalam sebagian besar kasus, ada beberapa pembersihan yang diperlukan. Fungsi useAsyncEffectseperti yang Anda tulis dapat dengan mudah menyesatkan seseorang untuk berpikir jika mereka mengembalikan fungsi pembersihan dari efek asinkronnya, fungsi itu akan dijalankan pada waktu yang tepat. Hal ini dapat menyebabkan kebocoran memori atau bug yang lebih buruk, jadi kami memilih untuk mendorong orang-orang untuk memfaktorkan ulang kode mereka untuk membuat "lapisan" fungsi asinkron yang berinteraksi dengan siklus hidup React lebih terlihat, dan akibatnya perilaku kode diharapkan lebih disengaja dan benar.
Sophie Alpert
8

Saya membaca pertanyaan ini, dan merasa cara terbaik untuk mengimplementasikan useEffect tidak disebutkan dalam jawaban. Katakanlah Anda memiliki panggilan jaringan, dan ingin melakukan sesuatu setelah Anda mendapat tanggapan. Demi kesederhanaan, mari simpan respons jaringan dalam variabel status. Seseorang mungkin ingin menggunakan tindakan / peredam untuk memperbarui penyimpanan dengan respons jaringan.

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]);

Referensi => https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

Chiranjib
sumber
2

void operator dapat digunakan di sini.
Dari pada:

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

atau

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

Anda bisa menulis:

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

Ini sedikit lebih bersih dan lebih cantik.


Efek asinkron dapat menyebabkan kebocoran memori, jadi penting untuk melakukan pembersihan pada pelepasan komponen. Dalam kasus pengambilan ini akan terlihat seperti ini:

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>;
}
kajkal.dll
sumber
1

mencoba

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

Nishla
sumber
1

Bagi pembaca lain, kesalahan bisa berasal dari fakta bahwa tidak ada tanda kurung yang membungkus fungsi asinkron:

Mempertimbangkan fungsi async initData

  async function initData() {
  }

Kode ini akan menyebabkan kesalahan Anda:

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

Tapi yang ini, tidak akan:

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

(Perhatikan tanda kurung di sekitar initData ()

Simon
sumber
1
Brilian, bung! Saya menggunakan saga, dan kesalahan itu muncul ketika saya memanggil pembuat tindakan yang mengembalikan satu-satunya objek. Sepertinya fungsi callback useEffect tidak menjilat perilaku ini. Saya menghargai jawaban Anda.
Gorr1995
2
Untuk berjaga-jaga jika orang bertanya-tanya mengapa ini benar ... Tanpa tanda kurung kurawal, nilai kembalian initData () secara implisit dikembalikan oleh fungsi panah. Dengan kurung kurawal, tidak ada yang dikembalikan secara implisit sehingga kesalahan tidak akan terjadi.
Marnix.hoh