Очищення пам'яті протікає на відключеному компоненті в реактивних гаках


19

Я новачок, що використовує React, тому це може бути дуже просто досягти, але я не можу сам це зрозуміти, хоча я провів деякі дослідження. Пробачте, якщо це занадто тупо.

Контекст

Я використовую Inertia.js з адаптерами Laravel (бекенд) і React (передній край). Якщо ви не знаєте інерції, це в основному:

Inertia.js дозволяє швидко створювати сучасні односторінкові програми React, Vue та Svelte, використовуючи класичну маршрутизацію та контролери на стороні сервера.

Проблема

Я роблю просту сторінку входу, яка має форму, яка при надходженні виконає запит POST для завантаження наступної сторінки. Здається, це працює добре, але на інших сторінках консолі відображається таке попередження:

Попередження: Неможливо виконати оновлення стану React на відключеному компоненті. Це неможливо, але це вказує на витік пам'яті у вашій програмі. Щоб виправити, скасувати всі підписки та асинхронні завдання у функції очищення useEffect.

увійти (створено Inertia)

Пов'язаний код (я спростив його, щоб уникнути невідповідних рядків):

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;

Тепер я знаю, що я повинен виконувати функцію очищення, оскільки обіцянка запиту - це те, що генерує це попередження. Я знаю, що мені слід користуватися, useEffectале не знаю, як це застосувати в цьому випадку. Я бачив приклад, коли значення змінюється, але як це зробити в такому дзвінку?

Заздалегідь спасибі.


Оновлення

Як вимагається, повний код цього компонента:

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;

@Sohail Я додав повний код компонента
Кенні Хорна

Ви намагалися просто видалити .then(() => {})?
Партизанський П

Відповіді:


22

Оскільки це виклик обіцянки асинхронізації, ви повинні використовувати змінну змінну ref (з useRef), щоб перевірити вже відключений компонент для наступного лікування відповіді на асинхронізацію (уникаючи витоку пам'яті):

Попередження: Неможливо виконати оновлення стану React на відключеному компоненті.

Дві гачки реагування, які ви повинні використовувати в цьому випадку: useRefі useEffect.

З useRef, наприклад, змінювані змінний _isMountedзавжди спрямований на одній і ті ж посилання в пам'яті (не локальна змінної)

useRef - це гачок, якщо потрібна змінна змінна. На відміну від локальних змінних, React гарантує, що те саме посилання повертається під час кожного візуалізації. Якщо ви хочете, те саме саме з цим.myVar у Class Component

Приклад:

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

З цього ж випадку дозвольте пояснити вам більше інформації про використані тут гачки React. Також я порівню React Hooks у функціональній складовій (версія React> 16.8) з LifeCycle in Class Component.

useEffect : Більшість побічних ефектів трапляються всередині гачка. Прикладами побічних ефектів є: отримання даних, налаштування підписки та вручну зміна DOM в компонентах React. UseEffect замінює багато LifeCycles у складі компонента класу (компонентDidMount, компонентDidUpate, компонентWillUnmount)

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

1) поведінка useEffect за замовчуванням запускається як після першого візуалізації (як ComponentDidMount), так і після кожного оновлення оновлення (як ComponentDidUpdate), якщо у вас немає залежностей. Це так:useEffect(fnc);

2) Надання масиву залежності для використанняEffect змінить його життєвий цикл. У цьому прикладі: useEffect буде викликаний один раз після першого візуалізації та кожного разу, коли змінюється підрахунок

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

   useEffect(fnc, [count]);
}

3) useEffect запуститься лише один раз після першого візуалізації (як ComponentDidMount), якщо ви поставите порожній масив для залежності. Це так:useEffect(fnc, []);

4) Щоб запобігти витоку ресурсів, все потрібно утилізувати після закінчення життєвого циклу гака (наприклад, ComponentWillUnmount) . Наприклад, із порожнім масивом залежності повернута функція буде викликана після відключення компонента. Це так:

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

useRef : повертає змінний об'єкт ref , властивість .current ініціалізується на переданий аргумент (InitiValue). Повернений об'єкт зберігатиметься протягом усього терміну експлуатації компонента.

Приклад: з наведеним вище запитом ми не можемо використовувати локальну змінну тут, оскільки вона буде втрачена та повторно ініційована при кожному оновленні оновлення.

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

  // ...
}

Таким чином, при поєднанні useRef і useEffect ми могли повністю очистити витоки пам'яті.


Хороші посилання, про які ви могли прочитати більше про Реакційні гачки:

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

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


1
Це спрацювало. Пізніше сьогодні я прочитаю надане посилання, щоб зрозуміти, як це вирішує проблему. Якщо ви можете детальніше розглянути відповідь, щоб включити деталі, було б чудово, тому це буде корисно для інших, а також нагородити вас після пільгового періоду. Дякую.
Кенні Хорна

Дякую за прийняття моєї відповіді. Я подумаю над вашим запитом і зроблю це завтра.
SanjiMika

0

Ви можете використовувати метод «» cancelActiveVisits з Inertiaскасувати активний visitв useEffectочищає гачку.

Так що при цьому дзвінку активний visitбуде скасований, а стан не оновлюватиметься.

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

якщо Inertiaзапит буде скасовано, він поверне порожню відповідь, тому вам доведеться додати додатковий чек, щоб обробити порожню відповідь. Додайте додавання блоку лову, а також для обробки будь-яких потенційних помилок.

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

Альтернативний спосіб (вирішення)

Ви можете використовувати useRefдля утримання статусу компонента, і на основі цього ви можете оновити state.

Проблема:

Воюючий показ проявляється тому, що the handleSubmitнамагається оновити стан компонента, навіть якщо компонент відключений від dom.

Рішення:

Встановіть прапорець , щоб тримати статус з component, якщо componentє , mountedто flagзначення буде trueі якщо componentце unmountedзначення прапора буде хибним. Отже, виходячи з цього, ми можемо оновити state. Для статусу прапора ми можемо використовувати useRefдля проведення посилання.

useRefповертає змінний об'єкт ref, .currentвластивість якого ініціалізовано до переданого аргументу (InitiValue). Повернений об'єкт зберігатиметься протягом усього терміну експлуатації компонента. У useEffectповертає функцію , яка буде встановлювати статус компонента, якщо вона демонтована.

І тоді в useEffectфункції очищення ми можемо встановити прапорfalse.

Використовуйте функцію очищення Effecr

useEffectКрюк дозволяє використовувати функцію очищення. Щоразу ефект більше не діє, наприклад, коли компонент, що використовує цей ефект, відключений, ця функція викликається для очищення всього. У нашому випадку ми можемо встановити прапор значення false.

Приклад:

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

І в handleSubmit ми можемо перевірити, чи компонент встановлений чи ні, і оновити стан на основі цього.

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

В іншому випадку встановіть значення " _componentStatusnull", щоб уникнути витоків пам'яті.


Це не спрацювало: /
Кенні Хорна

Не могли б ви затішити значення ajaxCallвсередині useEffect. і подивіться, яка цінність
Sohail

Вибачте за затримку. Це повертається undefined. Я додав його одразу післяreturn () => {
Кенні Хорна

Я змінив код. Будь ласка, спробуйте новий код.
Sohail

Я не скажу, що це виправлення або правильний спосіб вирішення цього питання, але це усуне попередження.
Sohail
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.