0

I'm writing my first react-typescript application and I'm seeing the following warning which may be causing some issue which I'm trying to solve.

index.js:1 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.

I'm not able to fathom this warning so could anyone help me out here to find out why this is happening.

The code where this error is occurring is following:

Finished.tsx

import Grid from "@material-ui/core/Grid";
import { useQuery } from "react-query";
import Book, { BookType } from '../Book/Book'
import LinearProgress from '@material-ui/core/LinearProgress';

const getBooks = async (): Promise<BookType[]> => 
    await (await fetch(`${process.env.REACT_APP_API_BASE_URL}/books/finished`, {
        headers: {'Content-Type': 'application/json'},
        credentials: 'include',
})).json()

const Finished = () => {
    
    const { isFetching, isLoading, isLoadingError, isError, data } = useQuery<BookType[] | undefined>(
        'books', 
        getBooks
    )
    
    if (isLoading || isFetching) {
        return  (
            <LinearProgress />
        )
    }

    if (isLoadingError || isError) {
        return (
            <h3 style={{color: "red", textAlign: "center"}}>
                Empty bookshelf
            </h3>
        )
    }

    return (
        <Grid container spacing={2}>
            {data?.map(book => (
            <Grid item key={book.id} xs={12} sm={2}>
                <Book book={book}/>
            </Grid>
            ))}
        </Grid>  
    )
}

export default Finished

Modal.tsx

import { SyntheticEvent, useState } from "react";
import { Modal, Button } from "react-bootstrap";
import { BookType } from '../Book/Book'
import UpdateForm from '../Components/UpdateForm'

const MyModal = (props: {show: boolean, book: BookType, setShow: (status: boolean) => void }) => {
    
  
    const [reload, setReload] = useState(false)
    const [error, setError] = useState(<></>)
    const bookId = props.book.id
    const bookName = props.book.name
    
    const handleClose = () => {
      props.setShow(false);
      if (reload) {
        window.location.reload()
      }
    }

    const deleteBook = async (e: SyntheticEvent) => {
      e.preventDefault()

      if (window.confirm("Are you sure?")) {
        const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/books/deletebook`, {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            credentials: 'include',
            body: JSON.stringify({
                "id": bookId,
                "name": bookName
            })
        });

        const content = await response.json();

        if (content.status !== 200) {
            setError((
                <div className="alert alert-danger" role="alert">
                    {content.message}
                </div>
            ))
        } else {
            setError((
                <div className="alert alert-warning" role="alert">
                    {content.message}
                </div>
            ))
            setReload(true)
        }
      }
    }

    return (
        <Modal
          show={props.show}
          onHide={handleClose}
          backdrop="static"
          keyboard={false}
        >
          <Modal.Header>
            <Modal.Title>
                <i>{bookName}</i>
            </Modal.Title>
          </Modal.Header>
          <Modal.Body>
          {error}
            <b><i>Book Status: {props.book.status.toUpperCase()}</i></b>
            <pre></pre>
            <UpdateForm bookId={bookId} setReload={setReload}/>
          </Modal.Body>
          <Modal.Footer>
            <Button variant="danger" onClick={deleteBook}>Delete Book</Button>
            <Button variant="secondary" onClick={handleClose}>
              Close
            </Button>
          </Modal.Footer>
        </Modal>
    );
}
  
export default MyModal

UpdateForm.tsx

import { useState, SyntheticEvent } from "react";

const UpdateForm = (props: {bookId: string, setReload: (reload: boolean) => void}) => {
    const [bookStatus, setBookStatus] = useState('reading')
    const [error, setError] = useState(<></>)
    const bookId = props.bookId

    const submit = async (e: SyntheticEvent) => {
        e.preventDefault();

        if (window.confirm(`Do you want to move this book to ${bookStatus}?`)) {
            const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/books/updatestatus`, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                credentials: 'include',
                body: JSON.stringify({
                    "status": bookStatus,
                    "id": bookId
                })
            });

            const content = await response.json();
            console.log(bookStatus)

            if (content.status !== 200) {
                setError((
                    <div className="alert alert-danger" role="alert">
                        {content.message}
                    </div>
                ))
            } else {
                setError((
                    <div className="alert alert-success" role="alert">
                        {content.message}
                    </div>
                ))
                props.setReload(true)
            }
        }
    }

    return (
        <div className="row">
            <div className="col">
                {error}
                <form className="row g-3" onSubmit={submit}>
                    <div className="col-md-6">
                    <select className="form-select" defaultValue="reading" required
                        onChange={e => setBookStatus(e.target.value)}
                    >
                        <option value="reading">Reading</option>
                        <option value="finished">Finished</option>
                        <option value="wishlist">Wishlist</option>
                    </select>
                    </div>
                    <div className="col-md-6">
                        <button type="submit" className="btn btn-primary">Update</button>
                    </div>
                </form>
            </div>
        </div>
    )
}

export default UpdateForm

The complete error stack trace is:

index.js:1 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.
    at UpdateForm (http://localhost:3000/static/js/main.chunk.js:1240:93)
    at div
    at http://localhost:3000/static/js/vendors~main.chunk.js:26915:27
    at div
    at div
    at http://localhost:3000/static/js/vendors~main.chunk.js:23980:23
    at div
    at Transition (http://localhost:3000/static/js/vendors~main.chunk.js:65502:30)
    at http://localhost:3000/static/js/vendors~main.chunk.js:22211:24
    at DialogTransition
    at http://localhost:3000/static/js/vendors~main.chunk.js:57183:24
    at http://localhost:3000/static/js/vendors~main.chunk.js:23657:23
    at MyModal (http://localhost:3000/static/js/main.chunk.js:692:85)
    at div
    at O (http://localhost:3000/static/js/vendors~main.chunk.js:71801:6)
    at Book (http://localhost:3000/static/js/main.chunk.js:505:81)
    at div
    at Grid (http://localhost:3000/static/js/vendors~main.chunk.js:1445:35)
    at WithStyles (http://localhost:3000/static/js/vendors~main.chunk.js:5136:31)
    at div
    at Grid (http://localhost:3000/static/js/vendors~main.chunk.js:1445:35)
    at WithStyles (http://localhost:3000/static/js/vendors~main.chunk.js:5136:31)

index.js:1 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.
    at MyModal (http://localhost:3000/static/js/main.chunk.js:692:85)
    at div
    at O (http://localhost:3000/static/js/vendors~main.chunk.js:71801:6)
    at Book (http://localhost:3000/static/js/main.chunk.js:505:81)
    at div
    at Grid (http://localhost:3000/static/js/vendors~main.chunk.js:1445:35)
    at WithStyles (http://localhost:3000/static/js/vendors~main.chunk.js:5136:31)
    at div
    at Grid (http://localhost:3000/static/js/vendors~main.chunk.js:1445:35)
    at WithStyles (http://localhost:3000/static/js/vendors~main.chunk.js:5136:31)
Pratik
  • 126
  • 9
  • It's a [bad practice to save rendered elements to the state](https://stackoverflow.com/a/47875226/1218980). Instead, use raw data to describe the state and render the messages based on that data. – Emile Bergeron Jun 22 '21 at 21:21
  • I've three states, normal state where noting will be displayed, then error true where the error will be displayed, and error false where a successful message will be displayed. I don't know if this could be achieved by this solution. Based on state CSS will also render. – Pratik Jun 23 '21 at 01:38
  • See it the other way around, it's never the solution to save the rendered elements in state, so, by that fact, it must then be possible to solve it using raw data. – Emile Bergeron Jun 23 '21 at 01:40

0 Answers0