Home > Software engineering >  Unable to fix React/NextJS memory leak using useEffect() and cleanup function
Unable to fix React/NextJS memory leak using useEffect() and cleanup function

Time:04-30

I've a pair of functional component hooks on a page, the AddVocab component adds new words to an array and ListVocab maps the array.

Everything works fine until I navigate away from the page, upon which I get this error:

react_devtools_backend.js:3973 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.

To fix this memory leak, I've tried many variations of useEffect() and cleanup function but to no avail. I know the cause is from the async VocabDataService Axios call not unsubscribing/cancelling when the components are unmounted.

Can anyone provide the correct useEffect() and cleanup function?

Here are the related files:

components/vocab/ListVocab.js

import React, { useState, useEffect } from "react";
import VocabDataService from "../../services/vocab";

export default function VocabList() {
    const [vocab, setVocab] = useState([]);
    const [currentVocab, setCurrentVocab] = useState(null);

    const retrieveVocab = () => {
        VocabDataService.getAll()
            .then((response) => {
                setVocab(response.data);
            })
            .catch((e) => {
                console.log(e);
            });
    };

    useEffect(() => retrieveVocab());

    const removeAllVocab = () => {
        VocabDataService.deleteAll()
            .then((response) => {
                setCurrentVocab(null);
            })
            .catch((e) => {
                console.log(e);
            });
    };

    return (
        <div>
            <div>
                <button onClick={removeAllVocab}>Delete</button>
                <ul>
                    {vocab.map((vocab, index) => (
                        <li key={index}>{vocab.word}</li>
                    ))}
                </ul>
            </div>
        </div>
    );
}

components/vocab/AddVocab.js

import React, { useState } from "react";
import VocabDataService from "../../services/vocab";

export default function AddVocab() {
    const [id, setId] = useState(null);
    const [word, setWord] = useState("");
    const [translation, setTranslation] = useState("");
    const [starred, setStarred] = useState(false);
    const [submitted, setSubmitted] = useState(false);

    const onChangeWord = (e) => {
        setWord(e.target.value);
    };

    const onChangeTranslation = (e) => {
        setTranslation(e.target.value);
    };

    const saveVocab = () => {
        var data = {
            word: word,
            translation: translation,
        };

        VocabDataService.create(data)
            .then((response) => {
                setId(response.data.id);
                setWord(response.data.word);
                setTranslation(response.data.translation);
                setStarred(response.data.starred);
                setSubmitted(true);
            })
            .catch((e) => {
                console.log(e);
            });
    };

    const newVocab = () => {
        setId(null);
        setWord("");
        setTranslation("");
        setStarred(false);
        setSubmitted(false);
    };

    return (
        <div className="submit-form">
            {submitted ? (
                <>{newVocab()}</>
            ) : (
                <div>
                    <div className="form-group">
                        <label htmlFor="word">Word:</label>
                        <input
                            type="text"
                            className="form-control"
                            id="word"
                            required
                            value={word}
                            onChange={onChangeWord}
                            name="word"
                        />
                    </div>

                    <div className="form-group">
                        <label htmlFor="translation">Translation:</label>
                        <input
                            type="text"
                            className="form-control"
                            id="translation"
                            required
                            value={translation}
                            onChange={onChangeTranslation}
                            name="translation"
                        />
                    </div>

                    <button onClick={saveVocab} className="btn btn-success">
                        Submit
                    </button>
                </div>
            )}
        </div>
    );
}

pages/vocab.js

import React from "react";
import Routes from "../components/Routes";
import AddVocab from "../components/vocab/AddVocab";
import VocabList from "../components/vocab/VocabList";

export default function App() {
    return (
        <div>
            <Routes />
            <div>
                <AddVocab />
                <VocabList />
            </div>
        </div>
    );
}

Thanks!

CodePudding user response:

useEffect has a cleanup function that sits in its return statement.

Try this:

`useEffect(() => {
     retrieve vocabulary()
 
     return removeAllVocab;
 });`

React will execute the function when it's time to "clean". That is, when the component is to be unmounted.

Take a look at the React doc

I hope it works for you

  • Related