update dom from child component to parent component

Viewed 93

I call an axios delete method in child component and want to refresh the parent component to react. When delete() called it deleted the row from database but it needs to reload manually. How could I do that without manual reloading?

I want to delete method works and the ProductList.js also update at the same time.

import React, { Component } from "react";
import axios from "axios";
import Table from "./Table";

class ProductList extends Component {
  constructor(props) {
    super(props);
    this.state = { productData: [] };
  }
  componentDidMount() {
    axios
      .get("http://localhost:8080/products")
      .then((response) => {
        console.log("responseProductList==", response);
        this.setState({ productData: response.data });
      })
      .catch(function (error) {
        console.log(error);
      });
  }
  tabRow() {
    return this.state.productData.map(function (object, i) {
      return <Table obj={object} key={i} />;
    });
  }
  render() {
    return (
      <div style={{ paddingTop: "25px" }}>
        <h4 align="center">Product List</h4>
        <table className="table table-striped" style={{ marginTop: 10 }}>
          <thead>
            <tr>
              <th>Id</th>
              <th>Name</th>
              <th>Price</th>
              <th colSpan="4">Action</th>
            </tr>
          </thead>
          <tbody>{this.tabRow()}</tbody>
        </table>
      </div>
    );
  }
}

export default ProductList;

import React, { Component } from "react";
import axios from "axios";
import { Link, withRouter } from "react-router-dom";

// import ProductList from "./ProductList";
// import AddProduct from "./AddProduct";

class Table extends Component {
  // constructor(props) {
  //   super(props);
  // }

  deleteProduct = () => {
    console.log("delete button clicked");
    axios
      .delete("http://localhost:8080/products/" + this.props.obj.id)
      .then((res) => {
        if (res.status === 200) {
          alert("getStatusInDelete");
          // return axios.get("http://localhost:8080/products/");
          // this.forceUpdate(<ProductList />);
          this.props.history.push("/ProductList");
        } else {
          console.log("Not refresh");
        }
      })
      .catch((err) => console.log(err));
  };

  render() {
    return (
      <tr>
        <td>{this.props.obj.id}</td>
        <td>{this.props.obj.name}</td>
        <td>{this.props.obj.price}</td>
        <td>
          <Link
            to={"/EditProduct/" + this.props.obj.id}
            className="btn btn-success"
          >
            Edit
          </Link>
        </td>
        <td>
          <button
            type="button"
            onClick={this.deleteProduct}
            className="btn btn-danger"
          >
            Delete
          </button>
        </td>
      </tr>
    );
  }
}

export default withRouter(Table);

3 Answers

You need to update the parent state when the product gets deleted successfully.

The way you can do it is defining the update logic in your parent component and then calling that function from your child component when delete gets successful.

// Define your update product logic here 
// It will get productId (or any other unique key) as parameter
// and will use that unique key to update the producsts 
// I've added an example logic but yours can be different
updateProducts = (productId) => {
    let productsClone = { ...this.state.productData };
    const productIndex = productsClone.findIndex(item => item.id == productId);

    if (productIndex > -1) {
        productsClone.splice(productIndex, 1);
        this.setState({ productData: productsClone });
    }
}

tabRow() {
    return this.state.productData.map(function (object, i) {
    // pass your `this.updateProducts` as a props to your child component
        return <Table obj={object} key={i} updateProducts={this.updateProducts} />;
    });
}

deleteProduct = () => {
    axios
        .delete("http://localhost:8080/products/" + this.props.obj.id)
        .then((res) => {
            if (res.status === 200) {
                // Finally, here, call the updateProucts when API return success and make sure to pass the correct key as a parameter    
                this.props.updateProducts(res.data.id);
            } else {
                console.log("Not refresh");
            }
        })
        .catch((err) => console.log(err));
};

First of all you shouldn't use the index as the key but instead use the id of the product because if you use the index you might encounter weird bugs after you delete. To update the list I would pass a callback onDelete(id) and handle it in the parent. Like this:

import React, { Component } from "react";
import axios from "axios";
import Table from "./Table";

class ProductList extends Component {
  constructor(props) {
    super(props);
    this.state = { productData: [] };
  }
  componentDidMount() {
    axios
      .get("http://localhost:8080/products")
      .then((response) => {
        console.log("responseProductList==", response);
        this.setState({ productData: response.data });
      })
      .catch(function (error) {
        console.log(error);
      });
  }

  //****** This is the callback you are going to pass ********
  onProductDeleted = (id) => {
    this.setState((state) => ({  productData: state.productData.filter(x => x.id !== id) })
  }
  
  tabRow() {
    return this.state.productData.map(object => {
      //********* Passing the callback ************
      return <Table obj={object} key={object.id} onDeleted={this.onProductDeleted} />;
    });
  }
  render() {
    return (
      <div style={{ paddingTop: "25px" }}>
        <h4 align="center">Product List</h4>
        <table className="table table-striped" style={{ marginTop: 10 }}>
          <thead>
            <tr>
              <th>Id</th>
              <th>Name</th>
              <th>Price</th>
              <th colSpan="4">Action</th>
            </tr>
          </thead>
          <tbody>{this.tabRow()}</tbody>
        </table>
      </div>
    );
  }
}

export default ProductList;

import React, { Component } from "react";
import axios from "axios";
import { Link, withRouter } from "react-router-dom";

class Table extends Component {

  deleteProduct = () => {
    console.log("delete button clicked");
    axios
      .delete("http://localhost:8080/products/" + this.props.obj.id)
      .then((res) => {
        if (res.status === 200) {
          alert("getStatusInDelete");
          this.props.history.push("/ProductList");
          
          //****** Invoke the callback after successfully deleted *****
          this.props.onDeleted(this.props.obj.id);
          
        } else {
          console.log("Not refresh");
        }
      })
      .catch((err) => console.log(err));
  };

  render() {
    return (
      <tr>
        <td>{this.props.obj.id}</td>
        <td>{this.props.obj.name}</td>
        <td>{this.props.obj.price}</td>
        <td>
          <Link
            to={"/EditProduct/" + this.props.obj.id}
            className="btn btn-success"
          >
            Edit
          </Link>
        </td>
        <td>
          <button
            type="button"
            onClick={this.deleteProduct}
            className="btn btn-danger"
          >
            Delete
          </button>
        </td>
      </tr>
    );
  }
}

export default withRouter(Table);

Here are some sources for you to go over that will improve your understanding of React:

  1. State updates might be asynchronous: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

  2. Here is why using indices as keys might be a bad idea: https://medium.com/@vraa/why-using-an-index-as-key-in-react-is-probably-a-bad-idea-7543de68b17c

Final answer: it is slightly edited and works fine now.

import React, { Component } from "react";
import axios from "axios";
import Table from "./Table";

class ProductList extends Component {
  constructor(props) {
    super(props);
    this.state = { productData: [] };
  }
  componentDidMount() {
    axios
      .get("http://localhost:8080/products")
      .then((response) => {
        console.log("responseProductList==", response);
        this.setState({ productData: response.data });
      })
      .catch(function (error) {
        console.log(error);
      });
  }

  //****** This is the callback you are going to pass ********
  onProductDeleted = (id) => {
    this.setState((state) => ({  productData: state.productData.filter(x => x.id !== id) })
    )}
  
  tabRow = () => {
    return this.state.productData.map(object => {
      //********* Passing the callback ************
      return <Table obj={object} key={object.id} onDeleted={this.onProductDeleted} />;
    });
  }
  render() {
    return (
      <div style={{ paddingTop: "25px" }}>
        <h4 align="center">Product List</h4>
        <table className="table table-striped" style={{ marginTop: 10 }}>
          <thead>
            <tr>
              <th>Id</th>
              <th>Name</th>
              <th>Price</th>
              <th colSpan="4">Action</th>
            </tr>
          </thead>
          <tbody>{this.tabRow()}</tbody>
        </table>
      </div>
    );
  }
}

export default ProductList;

import React, { Component } from "react";
import axios from "axios";
import { Link, withRouter } from "react-router-dom";

class Table extends Component {
  deleteProduct = () => {
    console.log("delete button clicked");
    axios
      .delete("http://localhost:8080/products/" + this.props.obj.id)
      .then((res) => {
        if (res.status === 200) {
          alert("getStatusInDelete");
          // this.props.history.push("/ProductList");

          //****** Invoke the callback after successfully deleted *****
          this.props.onDeleted(this.props.obj.id);
        } else {
          console.log("Not refresh");
        }
      })
      .catch((err) => console.log(err));
  };

  render() {
    return (
      <tr>
        <td>{this.props.obj.id}</td>
        <td>{this.props.obj.name}</td>
        <td>{this.props.obj.price}</td>
        <td>
          <Link
            to={"/EditProduct/" + this.props.obj.id}
            className="btn btn-success"
          >
            Edit
          </Link>
        </td>
        <td>
          <button
            type="button"
            onClick={this.deleteProduct}
            className="btn btn-danger"
          >
            Delete
          </button>
        </td>
      </tr>
    );
  }
}

export default withRouter(Table);

Related