Bersihkan memori yang bocor pada Komponen Tidak Terpasang di React Hooks

19

Saya baru menggunakan React, jadi ini mungkin sangat sederhana untuk dicapai tetapi saya tidak bisa mengetahuinya sendiri meskipun saya sudah melakukan penelitian. Maafkan saya jika ini terlalu bodoh.

Konteks

Saya menggunakan Inertia.js dengan adapter Laravel (backend) dan React (front-end). Jika Anda tidak tahu Inersia, itu pada dasarnya:

Inertia.js memungkinkan Anda dengan cepat membangun aplikasi React, Vue, dan Svelte satu halaman modern menggunakan perutean dan pengontrol sisi-server klasik.

Isu

Saya sedang melakukan halaman login sederhana yang memiliki formulir yang ketika dikirimkan akan melakukan permintaan POST untuk memuat halaman berikutnya. Tampaknya berfungsi dengan baik tetapi di halaman lain konsol menunjukkan peringatan berikut:

Peringatan: Tidak dapat melakukan pembaruan status React pada komponen yang dilepas. Ini adalah no-op, tetapi ini menunjukkan kebocoran memori di aplikasi Anda. Untuk memperbaiki, batalkan semua langganan dan tugas tidak sinkron dalam fungsi pembersihan useEffect.

dalam login (dibuat oleh Inersia)

Kode terkait (Saya telah menyederhanakannya untuk menghindari baris yang tidak relevan):

import React, { useEffect, useState } from 'react'
import Layout from "../../Layouts/Auth";

{/** other imports */}

    const login = (props) => {
      const { errors } = usePage();

      const [values, setValues] = useState({email: '', password: '',});
      const [loading, setLoading] = useState(false);

      function handleSubmit(e) {
        e.preventDefault();
        setLoading(true);
        Inertia.post(window.route('login.attempt'), values)
          .then(() => {
              setLoading(false); // Warning : memory leaks during the state update on the unmounted component <--------
           })                                   
      }

      return (
        <Layout title="Access to the system">
          <div>
            <form action={handleSubmit}>
              {/*the login form*/}

              <button type="submit">Access</button>
            </form>
          </div>
        </Layout>
      );
    };

    export default login;

Sekarang, saya tahu bahwa saya harus melakukan fungsi pembersihan karena janji permintaan adalah apa yang menghasilkan peringatan ini. Saya tahu bahwa saya harus menggunakan useEffecttetapi saya tidak tahu bagaimana menerapkannya dalam kasus ini. Saya telah melihat contoh ketika nilai berubah, tetapi bagaimana melakukannya dalam panggilan semacam ini?

Terima kasih sebelumnya.


Memperbarui

Seperti yang diminta, kode lengkap komponen ini:

import React, { useState } from 'react'
import Layout from "../../Layouts/Auth";
import { usePage } from '@inertiajs/inertia-react'
import { Inertia } from "@inertiajs/inertia";
import LoadingButton from "../../Shared/LoadingButton";

const login = (props) => {
  const { errors } = usePage();

  const [values, setValues] = useState({email: '', password: '',});

  const [loading, setLoading] = useState(false);

  function handleChange(e) {
    const key = e.target.id;
    const value = e.target.value;

    setValues(values => ({
      ...values,
      [key]: value,
    }))
  }

  function handleSubmit(e) {
    e.preventDefault();
    setLoading(true);
    Inertia.post(window.route('login.attempt'), values)
      .then(() => {
        setLoading(false);
      })
  }

  return (
    <Layout title="Inicia sesión">
      <div className="w-full flex items-center justify-center">
        <div className="w-full max-w-5xl flex justify-center items-start z-10 font-sans text-sm">
          <div className="w-2/3 text-white mt-6 mr-16">
            <div className="h-16 mb-2 flex items-center">                  
              <span className="uppercase font-bold ml-3 text-lg hidden xl:block">
                Optima spark
              </span>
            </div>
            <h1 className="text-5xl leading-tight pb-4">
              Vuelve inteligente tus operaciones
            </h1>
            <p className="text-lg">
              Recoge data de tus instalaciones de forma automatizada; accede a información histórica y en tiempo real
              para que puedas analizar y tomar mejores decisiones para tu negocio.
            </p>

            <button type="submit" className="bg-yellow-600 w-40 hover:bg-blue-dark text-white font-semibold py-2 px-4 rounded mt-8 shadow-md">
              Más información
            </button>
          </div>

        <div className="w-1/3 flex flex-col">
          <div className="bg-white text-gray-700 shadow-md rounded rounded-lg px-8 pt-6 pb-8 mb-4 flex flex-col">
            <div className="w-full rounded-lg h-16 flex items-center justify-center">
              <span className="uppercase font-bold text-lg">Acceder</span>
            </div>

            <form onSubmit={handleSubmit} className={`relative ${loading ? 'invisible' : 'visible'}`}>

              <div className="mb-4">
                <label className="block text-gray-700 text-sm font-semibold mb-2" htmlFor="email">
                  Email
                </label>
                <input
                  id="email"
                  type="text"
                  className=" appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 outline-none focus:border-1 focus:border-yellow-500"
                  placeholder="Introduce tu e-mail.."
                  name="email"
                  value={values.email}
                  onChange={handleChange}
                />
                {errors.email && <p className="text-red-500 text-xs italic">{ errors.email[0] }</p>}
              </div>
              <div className="mb-6">
                <label className="block text-gray-700 text-sm font-semibold mb-2" htmlFor="password">
                  Contraseña
                </label>
                <input
                  className=" appearance-none border border-red rounded w-full py-2 px-3 text-gray-700 mb-3 outline-none focus:border-1 focus:border-yellow-500"
                  id="password"
                  name="password"
                  type="password"
                  placeholder="*********"
                  value={values.password}
                  onChange={handleChange}
                />
                {errors.password && <p className="text-red-500 text-xs italic">{ errors.password[0] }</p>}
              </div>
              <div className="flex flex-col items-start justify-between">
                <LoadingButton loading={loading} label='Iniciar sesión' />

                <a className="font-semibold text-sm text-blue hover:text-blue-700 mt-4"
                   href="#">
                  <u>Olvidé mi contraseña</u>
                </a>
              </div>
              <div
                className={`absolute top-0 left-0 right-0 bottom-0 flex items-center justify-center ${!loading ? 'invisible' : 'visible'}`}
              >
                <div className="lds-ellipsis">
                  <div></div>
                  <div></div>
                  <div></div>
                  <div></div>
                </div>
              </div>
            </form>
          </div>
          <div className="w-full flex justify-center">
            <a href="https://optimaee.com">
            </a>
          </div>
        </div>
        </div>
      </div>
    </Layout>
  );
};

export default login;
Kenny Horna
sumber
@Sohail Saya telah menambahkan kode lengkap komponen
Kenny Horna
Apakah Anda mencoba menghapus saja .then(() => {})?
Guerric P

Jawaban:

22

Karena itu adalah panggilan janji async, jadi Anda harus menggunakan variabel ref yang dapat diubah (dengan useRef) untuk memeriksa komponen yang sudah di-unmount untuk perawatan selanjutnya dari respons async (menghindari kebocoran memori):

Peringatan: Tidak dapat melakukan pembaruan status React pada komponen yang dilepas.

Dua Kait Bereaksi yang harus Anda gunakan dalam kasus ini: useRefdan useEffect.

Dengan useRef, misalnya, variabel yang dapat berubah _isMountedselalu menunjuk pada referensi yang sama dalam memori (bukan variabel lokal)

useRef adalah tujuan untuk menghubungkan jika variabel yang dapat diubah diperlukan. Tidak seperti variabel lokal, React memastikan referensi yang sama dikembalikan selama setiap render. Jika Anda ingin, itu sama dengan this.myVar di Komponen Kelas

Contoh:

const login = (props) => {
  const _isMounted = useRef(true); // Initial value _isMounted = true

  useEffect(() => {
    return () => { // ComponentWillUnmount in Class Component
        _isMounted.current = false;
    }
  }, []);

  function handleSubmit(e) {
    e.preventDefault();
    setLoading(true);
    ajaxCall = Inertia.post(window.route('login.attempt'), values)
        .then(() => {
            if (_isMounted.current) { // Check always mounted component
               // continue treatment of AJAX response... ;
            }
         )
  }
}

Pada kesempatan yang sama, izinkan saya menjelaskan kepada Anda informasi lebih lanjut tentang React Hooks yang digunakan di sini. Juga, saya akan membandingkan React Hooks di Functional Component (versi React> 16.8) dengan LifeCycle in Class Component.

useEffect : Sebagian besar efek samping terjadi di dalam hook. Contoh efek samping adalah: pengambilan data, pengaturan langganan, dan perubahan DOM secara manual dalam komponen Bereaksi. UseEffect menggantikan banyak siklus hidup dalam komponen kelas (componentDidMount, componentDidUpate, componentWillUnmount)

 useEffect(fnc, [dependency1, dependency2, ...]); // dependencies array argument is optional

1) Perilaku default useEffect berjalan baik setelah render pertama (seperti ComponentDidMount) dan setelah setiap pembaruan render (seperti ComponentDidUpdate) jika Anda tidak memiliki dependensi. Seperti itu:useEffect(fnc);

2) Memberikan berbagai dependensi untuk menggunakanEffect akan mengubah siklus hidupnya. Dalam contoh ini: useEffect akan dipanggil sekali setelah render pertama dan setiap kali perubahan dihitung

export default function () {
   const [count, setCount] = useState(0);

   useEffect(fnc, [count]);
}

3) useEffect akan berjalan hanya sekali setelah render pertama (seperti ComponentDidMount) jika Anda meletakkan array kosong untuk dependensi. Seperti itu:useEffect(fnc, []);

4) Untuk mencegah kebocoran sumber daya, semuanya harus dibuang ketika siklus hidup sebuah kait berakhir (seperti ComponentWillUnmount) . Sebagai contoh, dengan array ketergantungan yang kosong, fungsi yang dikembalikan akan dipanggil setelah komponen dilepas. Seperti itu:

useEffect(() => {
   return fnc_cleanUp; // fnc_cleanUp will cancel all subscriptions and asynchronous tasks (ex. : clearInterval) 
}, []);

useRef : mengembalikan objek referensi yang dapat dirubah yang properti .current diinisialisasi ke argumen yang diteruskan (initialValue). Objek yang dikembalikan akan bertahan seumur hidup komponen.

Contoh: dengan pertanyaan di atas, kami tidak dapat menggunakan variabel lokal di sini karena itu akan hilang dan diinisiasi ulang pada setiap pembaruan render.

const login = (props) => {
  let _isMounted= true; // it isn't good because of a local variable, so the variable will be lost and re-initiated on every update render

  useEffect(() => {
    return () => {
        _isMounted = false;  // not good
    }
  }, []);

  // ...
}

Jadi, dengan kombinasi useRef dan useEffect , kita dapat sepenuhnya membersihkan kebocoran memori.


Tautan baik yang dapat Anda baca lebih lanjut tentang React Hooks adalah:

[EN] https://medium.com/@sdolidze/the-iceberg-of-react-hooks-af0b588f43fb

[FR] https://blog.soat.fr/2019/11/react-hooks-par-lexemple/

SanjiMika
sumber
1
Ini berhasil. Kemudian hari ini saya akan membaca tautan yang disediakan untuk benar-benar memahami cara mengatasi masalah ini. Jika Anda bisa menguraikan tanggapan untuk memasukkan rincian akan sangat bagus sehingga akan membantu orang lain dan juga untuk memberikan hadiah kepada Anda setelah masa tenggang. Terima kasih.
Kenny Horna
Terima kasih atas penerimaan Anda atas jawaban saya. Saya akan memikirkan permintaan Anda dan melakukannya besok.
SanjiMika
0

Anda bisa menggunakan metode 'cancelActiveVisits' dari Inertiauntuk membatalkan aktif visitdalam useEffectpembersihan kail.

Jadi dengan panggilan ini yang aktif visitakan dibatalkan dan negara tidak akan diperbarui.

useEffect(() => {
    return () => {
        Inertia.cancelActiveVisits(); //To cancel the active visit.
    }
}, []);

jika Inertiapermintaan dibatalkan maka akan mengembalikan respons kosong sehingga Anda harus menambahkan cek tambahan untuk menangani respons kosong. Tambahkan add catch block juga untuk menangani potensi kesalahan.

 function handleSubmit(e) {
    e.preventDefault();
    setLoading(true);
    Inertia.post(window.route('login.attempt'), values)
      .then(data => {
         if(data) {
            setLoading(false);
         }
      })
      .catch( error => {
         console.log(error);
      });
  }

Cara alternatif (solusi)

Anda dapat menggunakan useRefuntuk menahan status komponen dan berdasarkan ini Anda dapat memperbarui state.

Masalah:

Pertempuran itu muncul karena handleSubmit berusaha memperbarui keadaan komponen meskipun komponen telah dilepas dari dom.

Larutan:

Menetapkan bendera untuk memegang status component, jika componentini mountedmaka flagnilai akan truedan jika componentadalah unmountednilai flag akan palsu. Jadi berdasarkan ini kita dapat memperbarui state. Untuk status bendera dapat kita gunakan useRefuntuk menyimpan referensi.

useRefmengembalikan objek .currentreferensi yang dapat dirubah yang propertinya diinisialisasi ke argumen yang diteruskan (initialValue). Objek yang dikembalikan akan bertahan seumur hidup komponen. DiuseEffect gantinya fungsi yang akan mengatur status komponen, jika tidak di-mount.

Dan kemudian useEffect dalam fungsi pembersihan kita dapat mengatur benderafalse.

useEffecr fungsi pembersihan

The useEffectkait memungkinkan menggunakan fungsi pembersihan. Kapan saja efeknya tidak lagi valid, misalnya ketika komponen yang menggunakan efek itu tidak terpasang, fungsi ini dipanggil untuk membersihkan semuanya. Dalam kasus kami, kami dapat mengatur bendera menjadi false.

Contoh:

let _componentStatus.current =  useRef(true);
useEffect(() => {
    return () => {
        _componentStatus.current = false;
    }
}, []);

Dan di handleSubmit kita dapat memeriksa apakah komponen sudah terpasang atau tidak dan memperbarui status berdasarkan ini.

function handleSubmit(e) {
    e.preventDefault();
    setLoading(true);
    Inertia.post(window.route('login.attempt'), values)
        .then(() => {
            if (_componentStatus.current) {
                setLoading(false);
            } else {
                _componentStatus = null;
            }
        })
}

Di lain mengatur _componentStatuske nol untuk menghindari kebocoran memori.

Sohail
sumber
Itu tidak berhasil: /
Kenny Horna
Bisakah Anda menghibur nilai ajaxCalldalam useEffect. dan lihat apa nilainya
Sohail
Maaf atas keterlambatannya. Ia kembali undefined. Saya telah menambahkannya tepat setelahreturn () => {
Kenny Horna
Saya telah mengubah kode, Silakan coba kode baru.
Sohail
Saya tidak akan mengatakan bahwa ini adalah perbaikan atau cara yang benar untuk menyelesaikan masalah ini, tetapi ini akan menghapus peringatan.
Sohail