4

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;
Reema Parakh
  • 1,347
  • 3
  • 19
  • 46
  • Do not use `componentDidUpdate` you can use `componentWillReceiveProps` or `getderivedState` if using latest versions. – Just code Jan 09 '19 at 04:50
  • @Justcode `componentWillReceiveProps` is also not doing the thing. My react version is 16 – Reema Parakh Jan 09 '19 at 05:04
  • please create stackblitz link to reproduce the issue, with sample data – Just code Jan 09 '19 at 05:09
  • @Justcode I am not able to create a stackblitz link, as dusring API call its says: `net::ERR_SSL_PROTOCOL_ERROR`. I am having AWS EC2 server, where my from where I call my API – Reema Parakh Jan 09 '19 at 05:34

4 Answers4

0

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.

Krina Soni
  • 870
  • 6
  • 14
  • I tried this. But `componentDidUpdate()` method is not called. I am doing console.log("update"), and it prints only 2 times. If value in DB is changed and state is updated it is not catching the update. It didn't helped. – Reema Parakh Jan 09 '19 at 06:24
0
 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 – Shalini Sentiya Jan 09 '19 at 08:30
  • getSnapshotBeforeUpdate(prevProps, prevState) { if (!isEqual(prevState.activeData, this.state.activeData)) { this.getActiveData(); } return null; } use this method instead of componentDidUpdate(prevProps, prevState) – Shalini Sentiya Jan 09 '19 at 08:35
0
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;
  }
  • Did this. But console says- `index.js:1446 Warning: Active: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). This component defines getSnapshotBeforeUpdate() only.` and also state is not updated. – Reema Parakh Jan 09 '19 at 09:29
  • getSnapshotBeforeUpdate() method is a part of react:16.3, plz check the react version. – Shalini Sentiya Jan 09 '19 at 11:15
  • Any reason why this is not working? `if (prevState.activeData !== this.state.activeData) {this.getActiveData();}` – Reema Parakh Jan 10 '19 at 04:29
  • If activeData having the object inside the multiple object and array then !== sign can't be match the value with multiple object and array, it will always show the different and also match the reference of that property. just like activeData:{id:2323, users[{userId:"323", name:''"xyz", children: {}}, {userId:"323", name:''"xyz", children: {}}]} – Shalini Sentiya Jan 10 '19 at 05:06
  • Ok, so how will I manage state change, even `lodash` isEqual is also not effective here. – Reema Parakh Jan 10 '19 at 06:04
  • are you using the getActiveData() for update the activeData from api? – Shalini Sentiya Jan 11 '19 at 10:17
  • Yes, I have created this function for api call – Reema Parakh Jan 11 '19 at 10:19
  • first getActiveData() execution from componentDidMount() for that time activeData will be null after the execution it will having the value but i am not able to find out how to update name value in activeData and where to change? – Shalini Sentiya Jan 11 '19 at 10:25
  • the data is from api which is created in Django/PostgreSQL. If for example I manually change the name field in DB or I update the record from admin panel, then value gets changed, and that changed value should be reflected. – Reema Parakh Jan 11 '19 at 10:28
  • if you change the value from admin panel or data base then you should refresh the Frontend UI because ui dont know admin changed the field value or state activeData don't be updated in active component. componentDidUpdate() is not a good solution for update the state because it will not execute for exp:- this.state.activeData = [] after the execution of getActiveData() activeData Sate will updated for that time this.state and prev.state having same data so it will not execute. if you will use the getSnapshotBeforeUpdate() it will execute only once. – Shalini Sentiya Jan 11 '19 at 11:16
  • for exp:- after the execution of getActiveData() this.sate.active data will update then call the getSnapshotBeforeUpdate() because this.sate.activeData and prev sate will be different but before the second time execution of getActiveData() admin don't have any changes on filed then both state will be same then this getSnapshotBeforeUpdate() will not be execute even if admin changes the field. – Shalini Sentiya Jan 11 '19 at 11:22
  • So `getSnapshotBeforeUpdate()` will never be called for array of objects, if any value is changed or if there is a new record? is that what it means? – Reema Parakh Jan 12 '19 at 11:27
0

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();
}
Kyaw Kyaw Soe
  • 3,258
  • 1
  • 14
  • 25