Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does anyone have this issue? "To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method"

I run into a following message while implementing react application. Does anyone have same issue like this?

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 the componentWillUnmount method. in ProductList (at App.js:44)

My entry page is ProductList component. After loading entry page, if I click the LogOut in the header, I run into that message. Does anyone have some advice for it?

So I referred to several answers for it like

Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application

However I can't solve it, why this happens. :(

App.js

import React, { Component } from "react";
import { Route } from "react-router-dom";

import Header from "./Header";
import ProductList from "./ProductList";
import Login from "./Login";
import Logout from "./Logout";
import "./App.css";

class App extends Component {
  constructor() {
    super();
    this.state = {
      query: "",
      isLoggedIn: false
    };
    this.handleLoginStatus = this.handleLoginStatus.bind(this);
    this.handleLogoutStatus = this.handleLogoutStatus.bind(this);
    this.setSearchKeyword = this.setSearchKeyword.bind(this);
  }

  handleLoginStatus() {
    this.setState({ isLoggedIn: true });
  }

  handleLogoutStatus() {
    this.setState({ isLoggedIn: false });
  }

  setSearchKeyword(query) {
    this.setState({
      query: query
    });
  }

  render() {
    return (
      <div>
        <Header setSearchKeyword={this.setSearchKeyword} />
        <Route
          path="/"
          exact={true}
          render={() => (
            <ProductList
              query={this.state.query}
              isLoggedIn={this.state.isLoggedIn}
            />
          )}
        />
        <Route
          path="/login"
          render={() => (
            <Login
              isLoggedIn={this.state.isLoggedIn}
              handleLoginStatus={this.handleLoginStatus}
            />
          )}
        />
        <Route
          path="/logout"
          render={() => (
            <Logout
              isLoggedIn={this.state.isLoggedIn}
              handleLogoutStatus={this.handleLogoutStatus}
            />
          )}
        />
      </div>
    );
  }
}

export default App;

ProductList.js

import React, { PureComponent } from "react";
import { Table } from "react-bootstrap";

import axios from "axios";

class ProductList extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      products: null,
      loaded: false
    };
  }

  // componentWillReceiveProps(nextProps) {
  //   console.log(nextProps.query);
  // }

  componentDidUpdate() {
    const url =
      "https://localhost/product/search?query=" + this.props.query;

    const options = {
      method: "GET",
      headers: {
        Authorization: "Bearer " + localStorage.getItem("auth-token")
      },
      url
    };

    axios(options)
      .then(response => {
        let products = response.data;
        this.setState({ products: products });
      })
      .catch(error => {
        console.log("axios error", error);
      });
  }

  componentDidMount() {
    const url =
      "https://localhost/product/search?query=" + this.props.query;

    const options = {
      method: "GET",
      headers: {
        Authorization: "Bearer " + localStorage.getItem("auth-token")
      },
      url
    };

    axios(options)
      .then(response => {
        let products = response.data;
        this.setState({ products: products, loaded: true });
      })
      .catch(error => {
        console.log("axios error", error);
      });
  }

  // ComponentWillUnmount() {
  //   this.isUnmounted = true;
  // }

  render() {
    if (this.state.loaded) {
      let columnNames = ["Num", "Name", "Indications", "features"];
      let fieldNames = ["num", "name", "indications", "features"];

      var tableHeaders = (
        <tr>
          {columnNames.map(column => {
            return <th key={column}>{column}</th>;
          })}
        </tr>
      );

      var tableBody = this.state.products.map((product, i) => {
        return (
          <tr key={product + "_" + i}>
            {fieldNames.map((field, j) => {
              if (j === 0) {
                return <td key={product.name + "_" + i + "_" + j}>{i + 1}</td>;
              } else {
                return (
                  <td key={product.name + "_" + i + "_" + j}>{product[field]}</td>
                );
              }
            })}
          </tr>
        );
      });
    }
    return (
      <div>
        <Table striped bordered condensed hover>
          <thead>{tableHeaders}</thead>
          <tbody>{tableBody}</tbody>
        </Table>
      </div>
    );
  }
}

export default ProductList;

If you give some advice, it will be great help.

like image 448
Anna Lee Avatar asked Dec 01 '18 20:12

Anna Lee


Video Answer


1 Answers

The problem is inside componentDidUpdate and because this.setState is asynchronous action an error shows up.

How does it happen?

  1. Logout action happens
  2. ProductList componentDidUpdate is not guarded by any condition and this.setState being called infinitely.
  3. ProductList componentWillUnmount triggers
  4. Asynchronous this.setState({ products: products }) action from componentDidUpdate tries to update state
  5. An error shows up

To fix your problem add a condition inside componentDidUpdate.

Official docs about componentDidUpdate

You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop. It would also cause an extra re-rendering which, while not visible to the user, can affect the component performance. If you’re trying to “mirror” some state to a prop coming from above, consider using the prop directly instead. Read more about why copying props into state causes bugs.

Example solution:

componentDidUpdate(prevProps) {
   // Please do not forget to compare props
   if (this.props.somethingChanged !== prevProps.somethingChanged) {
     // this.setState logic here
   }
}
like image 156
loelsonk Avatar answered Sep 30 '22 05:09

loelsonk