• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Najlepsze rozwiązanie na React memory leak

VPS Starter Arubacloud
0 głosów
261 wizyt
pytanie zadane 4 marca 2022 w JavaScript przez Bakkit Dyskutant (7,600 p.)

Cześć.

Pisząc swoją aplikację w React napotkałem taki błąd:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

Przyczyną błędu jest próba aktualizowania stanu aplikacji w hooku useEffect (z pomocą biblioteki axios). Po małym researchu natrafiłem na coś takiego:

useEffect(() => {
  let isMounted = true;
  someAsyncOperation().then(data => {
    if (isMounted) setState(data);
  })
  return () => { isMounted = false };
}, []); 

Czysto teoretycznie ten przykład działa, lecz wydaje mi się to trochę zaśmiecaniem kodu. W dodatku gdy, w niektórych przypadkach fetch wykonuje się nie w useEffect, a w osobnej funkcji wywoływanej na zdarzenie onClick kodu dochodzi jeszcze więcej.

Czytałem również o odwoływaniu zapytań axios'a, ale to dalej nie rozwiązuje problemu ze zdarzeniem kliknięcia.

Czy macie na to jakieś lepsze rozwiązania?

1 odpowiedź

+1 głos
odpowiedź 4 marca 2022 przez ScriptyChris Mędrzec (190,190 p.)

A jakbyś informację o stanie zamontowania komponentu trzymał w custom ref?

const componentState = useRef({ operational: null });

useEffect(() => {
  componentState.current.operational = true;

  return () => {
    componentState.current.operational = false;
  };
}, []);

const handleClick = () => {
  someAsyncOperation().then(data => {
    if (componentState.current.operational) {
      setState(data);
    }
  })
}

W sumie, w ref można by trzymać wartość prymitywną zamiast obiektu.

komentarz 4 marca 2022 przez Bakkit Dyskutant (7,600 p.)

Po co do useRef przekazywać obiekt skoro od razu można użyć boolean'a?

O useRef też myślałem, o ile rozwiązuje on zdarzenie kliknięcia tak nie wygląda to zbyt czytelnie (przynajmniej jak dla mnie). Mowa o kodzie, który oprócz then, wykorzystuje również catch, oraz finally, a więc kod:

const handleClick = () => {
  someAsyncOperation().then(data => {
    if (componentState.current.operational) {
      // kod
    }
  })
  .catch(() => {
    if (componentState.current.operational) {
      // kod
    }
  })
  .finally(() => {
    if (componentState.current.operational) {
      // kod
    })
  })
}

+ dodatkowe parę linijek useEffect, no trochę mi zaśmieca, tym bardziej, że piszę aplikację a'la social media, gdzie leci mnóstwo zapytań do api w różnych komponentach.

Jeśli to jedyne sensowne rozwiązanie to no to chyba je zastosuję z konieczności, ale poczekam może na jakieś inne pomysły.

komentarz 4 marca 2022 przez ScriptyChris Mędrzec (190,190 p.)

Po co do useRef przekazywać obiekt skoro od razu można użyć boolean'a?

Pod kodem dopisałem, że "W sumie, w ref można by trzymać wartość prymitywną zamiast obiektu.".


Można też pokombinować z fabryką AbortController-ów w axios. Jeśli obsługę HTTP masz w osobnym module/serwisie, to anulowanie z perspektywy komponentów powinno ograniczać się do zawołania funkcji z serwisu HTTP w momencie, gdy komponent jest wymontowany i to by anulowało wszystkie aktywne requesty z axiosa dla danego komponentu. Dodatkowo, można to opakować we własny hook, żeby ograniczyć do minimum boilerplate poszczególnych komponentów.

Coś w ten deseń (traktuj bardziej jako kod ideowy niż działająca implementacja):

const subscribedComponents = {}; // if you want to cache components subscribed to abortable requests

function createSendRequest(controller) {
  return () => { // return function remembering `controller` instance
    return axios.get('/foo/bar', {
       signal: controller.signal // bind request with controller signal
    })
  }
}

function subscribeToAborts(componentName) {
  const controller = new AbortController();

  // list all request function with closures over `controller` here
  const sendRequest = createSendRequest(controller); // create individual request with curied controller reference

  const sub = { 
    sendRequest,
    abortRequests: () => controller.abort() // abort all requests
  };
 
   // TODO: handle duplicate subscriptions  
   subscribedComponents[componentName] = sub; // some caching
  
  return sub;
};

export { subscribeToAborts }

 

import React, { useEffect, useState } from 'react';
import httpService from 'path/to/http-service';

function useSubscribeToAbortableHTTP(componentName) {
  const [abortableSub, setAbortableSub] = useState({});

  useEffect(() => {
    const subscribedAbort = httpService.subscribeToAborts(componentName);
    setAbortableSub(subscribedAbort);

    return () => subscribedAbort.abortRequests();
  }, [])

  return abortableSub;
}

export { useSubscribeToAbortableHTTP }
const { useSubscribeToAbortableHTTP } from 'path/to/abort-requests-hook.js';

function MyComponent() {
  const abortableHTTP = useSubscribeToAbortableHTTP('MyComponent')

  const handleClick = () => {
    abortableHTTP
      .sendRequest(); // send abortable request
      .then((response) => {
         // do stuff with response
      })
  };

  return <button onClick={handleClick}>Kliknij mnie</button>
}

I wtedy w żadnym indywidualnym zawołaniu jakiejś funkcji HTTP w komponentach nie powinieneś musieć sprawdzać czy komponent jest już odpięty czy nie - bo tym się zajmie serwis HTTP i własny (generyczny) hook.

Podobne pytania

0 głosów
0 odpowiedzi 292 wizyt
+2 głosów
2 odpowiedzi 314 wizyt
pytanie zadane 30 stycznia 2022 w JavaScript przez ferdynand Obywatel (1,250 p.)
0 głosów
1 odpowiedź 845 wizyt
pytanie zadane 13 lutego 2019 w JavaScript przez Szymson Nowicjusz (120 p.)

92,451 zapytań

141,261 odpowiedzi

319,073 komentarzy

61,853 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Akademia Sekuraka 2024 zapewnia dostęp do minimum 15 szkoleń online z bezpieczeństwa IT oraz dostęp także do materiałów z edycji Sekurak Academy z roku 2023!

Przy zakupie możecie skorzystać z kodu: pasja-akademia - użyjcie go w koszyku, a uzyskacie rabat -30% na bilety w wersji "Standard"! Więcej informacji na temat akademii 2024 znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...