Johnson Khoo
Johnson Khoo

Reputation: 43

ReactJS state updates one step behind

I am trying to create a simple table using ReactJS to display user information. Here's how the general code structure looks like:

class ParentComponent extends React.Component {
  state = { 
    data : [] 
  }

  componentDidMount() {
    // initializes state with data from db
    axios.get("link/").then(res => {
      this.setState({data: res.data});
    });

    // I should be able to call this.getData() instead
    // of rewriting the axios.get() function but if I do so,
    // my data will not show up

  }
  
  // retrieves array of data from db
  getData = () => {
    axios.get("link/").then(res => {
      this.setState({data: res.data});
    });
  }  

  render() {
    return (
      <div>
        <ChildComponent data={this.state.data} refetch={this.getData} />
      </div>
    )
  }
}

Each of the generated rows should have a delete function, where I'll delete the entry from the database based on a given id. After the deletion, I want to retrieve the latest data from the parent component to be redisplayed again.

class ChildComponent extends React.Component {

  // deletes the specified entry from database
  deleteData = (id) => {
    axios.get("deleteLink/" + id).then(res => {
      console.log(res);
      // calls function from parent component to 
      // re-fetch the latest data from db
      this.props.refetch();

    }).catch(err => {console.log(err)});
  }

  render() {
    let rows = null;
    if(this.props.data.length) {
      // map the array into individual rows
      rows = this.props.data.map(x => {
        return (
          <tr>
            <td>{x.id}</td>
            <td>{x.name}</td>
            <td>
               <button onClick={() => {
                 this.deleteData(x.id)
                }}>
                  Delete
               </button>
            </td>
          </tr>
        )
      })

    }

    return (
      <div>
        <table>
          <thead></thead>
          <tbody>
            {rows}
          </tbody>
        </table>
      </div>
    )
  }
}

The two problems which I encountered here are:

  1. Logically, I should be able to call this.getData() from within componentDidMount(), but if I do so, the table doesn't load.
  2. Whenever I try to delete a row, the table will not reflect the update even though the entry is removed from the database. The table will only be updated when I refresh the page or delete another row. Problem is, the component is always lagging behind by 1 update.

So far I have tried:

  1. this.forceUpdate() - doesn't work
  2. this.setState({}) - empty setState doesn't work either
  3. changing componentDidMount() to componentDidUpdate() - error showing that I have "reached maximum depth" or something along that line
  4. adding async await in front of axios - doesn't work

Any help is appreciated. Thanks in advance.


EDIT: I did some debugging and tracked down the issue, which is not relevant to my question. My deleteData() which is located in ChildComponent uses axios.post() instead of axios.get(), which I overlooked.

deleteData = (id) => {
    axios.post("deleteLink/", id).then(res => {
      console.log(res);
      // calls function from parent component to 
      // re-fetch the latest data from db
      this.props.refetch();

    }).catch(err => {console.log(err)});
  }

In order for axios.post() to return a response, in order to perform .then(), you'll need to add a res.json() to the routing codes.

Upvotes: 4

Views: 511

Answers (2)

brein
brein

Reputation: 1419

I can't find an issue in your code, the only way I can help is to tell you how I would debug it.

edit parent like so:

class ParentComponent extends React.Component {
  state = { 
    data : [] 
  }

  componentDidMount() {
    // You are right when you say this should works, so 
    // stick to it until the bug is fixed
    
    console.log("parent mounted");
    this.getData();
  }
  
  // retrieves array of data from db
  getData = () => {
    console.log("fetching data");

    axios.get("link/").then(res => {
      console.log("fetched data", res.data);
      this.setState({data: res.data});
    });
  }  

  render() {
    return (
      <div>
        <ChildComponent 
          data={this.state.data}
          refetch={this.getData}
        />
      </div>
    )
  }
}

and in the child component add these 2 lifecycle methods just for debugging purposes:

componentDidMount () {
   console.log("child mounted", this.props.data)     
}

componentDidUpdate (prevProps) {
   console.log("old data", prevProps.data);
   console.log("new data", this.props.data);
   console.log("data are equal", prevProps.data === this.props.data);
}

if you can share the logs I can try help you more

Upvotes: 1

b3hr4d
b3hr4d

Reputation: 4578

You should map data into your child.

Change your parent like this:

class ParentComponent extends React.Component {
  state = { 
      data : [] 
  }

  componentDidMount() {
      this.getData();
  }
    
  getData = () => axios.get("link/").then(res => this.setState({data: res.data});
    
  deleteData = (id) => axios.get("deleteLink/" + id).then(res => this.getData())
       .catch(err => { console.log(err) });

  render() {
    return (
      <div>
        <table>
          <thead></thead>
          <tbody>
            {this.state.data.map(x => <ChildComponent row={x} deleteData={this.deleteData} />)}
          </tbody>
        </table>
      </div>
    )
  }
}

And your child component should be like this

const ChildComponent = ({row,deleteData}) => (
      <tr>
        <td>{row.id}</td>
        <td>{row.name}</td>
        <td><button onClick={() => deleteData(row.id)}>Delete</button></td>
      </tr >
    )

Upvotes: 3

Related Questions