ReactJS component state is not updating when any value in state array is modified

Viewed 1826

In my React component, I want my state to automatically update without reloading the page. I am using lodash.iseqaual method to compare the state change, but this not updating the state.

Below is my component:

import React, { Component } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
import $ from "jquery";
import isEqual from "lodash/isEqual";
$.DataTable = require("datatables.net");

class Active extends Component {
  state = {
    activeData: [],
    flag: 0,
    isLoading: true
  };

  componentDidMount() {
    this.getActiveData();
  }

  componentDidUpdate(prevProps, prevState) {
    if (!isEqual(prevState.activeData, this.state.activeData)) {
      this.getActiveData();
    }
  }

  getActiveData() {
    const params = new FormData();
    params.append("status", "active");
    axios.post("http://127.0.0.1:8000/details/", params).then(res => {
      if (res.data.result === 1) {
        this.setState({
          activeData: res.data.data,
          flag: 1,
          isLoading: false
        });
        if (!this.state.isLoading) {
          this.initTable();
        }
      } else if (res.data.result === 0) {
        this.setState({ isLoading: false });
        this.initTable();
      }
    });
  }

  initTable() {
    $("#Active_Datatable").DataTable();
  }

  render() {
    if (this.state.isLoading) {
      return (
        <img
          alt="loading"
          src="https://upload.wikimedia.org/wikipedia/commons/b/b1/Loading_icon.gif"
        />
      );
    }
    return (
      <React.Fragment>
        <div className="panel-heading" role="tab" id="heading3">
          <a
            href="#Current"
            className="collapsed text-left textuppercase"
            role="button"
            data-toggle="collapse"
            data-parent="tabs-content"
            aria-expanded="true"
            aria-controls="Current"
          >
            <i className="fas fa-list-ul" /> Current
            <i className="fas fa-chevron-down pull-right" />
          </a>
        </div>

        <div
          id="Current"
          role="tabpanel"
          className="tab-pane panel-collapse collapse"
          aria-labelledby="heading3"
        >
          <table
            id="Active_Datatable"
            className="display"
            style={{ width: "100%" }}
          >
            <thead>
              <tr>
                <th>#</th>
                <th>Name</th>
                <th>Open</th>
                <th>Close</th>
                <th>Price</th>
              </tr>
            </thead>
            {this.state.flag === 1 ? (
              <tbody>
                {this.state.activeData.map((ac, i) => (
                  <tr key={sc.id}>
                    <td className="text-center">{i + 1}</td>
                    <td>
                      <Link
                        to={{
                          pathname: `/${ac.name_slug}/`,
                          state: {
                            id: ac.id,
                            name: ac.name
                          }
                        }}
                      >
                        {sc.name}
                      </Link>
                    </td>
                    <td>{sc.open_date}</td>
                    <td>{sc.close_date}</td>
                    <td>
                      {Number(sc.price).toLocaleString("en-IN", {
                        currency: "INR"
                      })}
                    </td>
                  </tr>
                ))}
              </tbody>
            ) : null}
          </table>
        </div>
      </React.Fragment>
    );
  }
}

export default Active;

The lodash.isequal in componentDidUpdate() method is not updating my state.

I have also tried the below logic in componentDidUpdate:

componentDidUpdate(prevProps, prevState) {
    if (prevState.activeData !== this.state.activeData) {
      this.getActiveData();
    }
}

But this goes inifinte loop, and which increases the API call, even after there is no change.

How can I achieve this?

Thanks in advance!

**In need waiting for a solution to this problem.

UPDATE:

From all answers I guess I should explain with sample example. So when i make a API call and update state activeData, it updated as:

[
 {
   id: 1
   name: "ABC"
   close_date: "2019-01-11"
   open_date: "2019-01-08"
   price: "80"
 },
 {
   id: 2
   name: "XYZ"
   close_date: "2019-01-12"
   open_date: "2019-01-04"
   price: "80"
 }
]

If for example I update the name field from ABC to ABCD the returned result will be:

[
 {
   id: 1
   name: "ABCD"
   close_date: "2019-01-11"
   open_date: "2019-01-08"
   price: "80"
 },
 {
   id: 2
   name: "XYZ"
   close_date: "2019-01-12"
   open_date: "2019-01-04"
   price: "80"
 }
]

And then the name value in component should automatically be updated from ABC to ABCD, without reloading the page.

This is not happening with either shouldComponentUpdate or by changing my lodash syntax as suggested.

Directory structure:

project
  |- public
  |- src
     -components
        -Header_Footer
          Header.jsx
          Footer.jsx
        -Home
          index.jsx
          -Details
             Active.jsx
             Upcoming.jsx
     App.js
     index.js    

Also I would tell how my components are being rendered:

Details.jsx

import React, { Component } from "react";
import Active from "./Active";
import Upcoming from "./Upcoming";

class Details extends Component {

  render() {
    return (
      <div className="col-md-9 col-sm-9 col-xs-12">
        <div className="right_panel">
          <h2>Listing</h2>

          <div className="responsive-tabs text-center ">
            <ul className="nav nav-tabs" role="tablist">
              <li role="presentation" className="active">
                <a
                  href="#Upcoming"
                  aria-controls="Upcoming"
                  role="tab"
                  data-toggle="tab"
                >
                  Upcoming
                </a>
              </li>
              <li role="presentation" className="">
                <a
                  href="#Current"
                  aria-controls="Current"
                  role="tab"
                  data-toggle="tab"
                >
                  Current
                </a>
              </li>

            </ul>
            <div
              id="tabs-content"
              className="tab-content panel-group table-responsive"
            >
              <Upcoming />
              <Active />
            </div>
          </div>
        </div>
      </div>
    );
  }
}

export default Details;

Home.js

import React, { Component } from "react";
import Sidebar from "./Sidebar";
import Details from "./details";

class Home extends Component {

  render() {
    return (
      <div className="container container_padding">
        <div className="row">
          <Sidebar />
          <Details />
        </div>
      </div>
    );
  }
}

export default Home;

App.js

import React, { Component } from "react";
import { Router, Route, Switch } from "react-router-dom";
import Header from "./components/Header_Footer/Header";
import Footer from "./components/Header_Footer/Footer";
import Home from "./components/Home";
import createBrowserHistory from "history/createBrowserHistory";
const history = createBrowserHistory();

class App extends Component {
  render() {
    return (
      <Router history={history}>
        <React.Fragment>
          <Header />
          <Switch>
            <Route exact path="/" component={Home} />
          </Switch>
          <Footer />
        </React.Fragment>
      </Router>
    );
  }
}

export default App;
4 Answers

I suppose you have some syntax error as per the Lodash Docs, you need to add underscore before function.

Try doing as follow:

if (!_.isEqual(prevState.activeData, this.state.activeData)) {
      this.getActiveData();
    }

Also, Please check two arrays are unique or not and comparison is happening at the moment or not. That's where your issue is.

 you can also use the lodash package from this 
 https://www.npmjs.com/package/lodash

 $ npm i --save lodash        

 import isEqual from "lodash/isEqual";

 componentDidUpdate(prevProps, prevState) {
   if (!isEqual(prevState.activeData, this.state.activeData)) {
      this.getActiveData();
   }
 }
isEqual method is working Properly, You have need to change componentDidUpdate() method from getSnapshotBeforeUpdate() method because componentDidUpdate(prevProps, prevState) method execute after update the state so prevState having the same value which are having the this.state.activeData.

getSnapshotBeforeUpdate(prevProps, prevState) {
    if (!isEqual(prevState.activeData, this.state.activeData)) {
      this.getActiveData();
    }
    return null;
  }

getActiveData inside componentDidUpdate will be called only one time, on first time when componentDidUpdate occur, and it will never be called again because prevState.activeData never change.

you can try the other way, trigger getActiveData() on componentDidUpdate and componentDidUpdate, use shouldComponentUpdate to decide that component should update or not, here the code

componentDidMount() {
    this.getActiveData();
}

shouldComponentUpdate(nextProps, nextState){
   return !isEqual(nextState.activeData, this.state.activeData)
}

componentDidUpdate() {
    this.getActiveData();
}
Related