简体   繁体   中英

React: Re-render parent component based on Child component

I have one parent component, named Cart and another component named Items . Basically, I am getting an array of objects from Node.js and save it as a state in Cart component.

My Cart Component:

class Cart extends React.Component {
    constructor(props){
        super(props)

        this.state={
            cartData:null,
        }

    }

    componentWillUnmount(){
        _isMounted = false

    }

    componentDidMount() {
        _isMounted = true
        this.callApi()
        .then(res => {if(_isMounted) this.setState({cartData: res.express})})
        .catch(err => console.log(err));
      }

    callApi = async () => {
        const response = await fetch('/myCart');
        const body = await response.json();
        if (response.status !== 200) throw Error(body.message);

        return body;

    };

    mapOutItems(){
        return this.state.cartData.map((item,index) => (
            <Item
                key={index}
                desc={item.desc}
                amount={item.amount}
                total={item.total}
                delete={this.handleDelete(item.desc)}
            />
        ))
    }
    handleDelete(desc){
        axios({method: "post", url: "/remove", data:{name: desc}})

        axios("/myCart").then(function (response){
            this.setState({cartData : response.data.express})
        })
        .catch(function (error){
            console.log(error)
        });
    }

    render(){
        return(
            <div>
                <div className="container">
                    {this.mapOutItems()}
                </div>
            </div>
        )
    }
}

export default Cart;

Item Component

class Item extends React.Component{
    handleClick(e){
        e.preventDefault()
        axios({method: "post", url: "/remove", data:{name: this.props.desc}})
    }
    render(){
        return(
            <div>
                <div className="row">
                    <div className="col">
                        {this.props.desc}
                    </div>
                    <div className="col">
                        {this.props.amount}
                    </div>
                    <div className="col">
                        {this.props.total}
                    </div>
                    <button className="btn btn-sm btn-danger" onClick=onClick={this.props.delete}>
                        Delete
                    </button>
                </div>
            </div>
        )
    }
}

export default Item;

Logic: My Cart component will get data from the endpoint at Node.js. It is an array of objects and Cart component use it to populate the item component. The item component has a delete button that once trigger will send the name of the item it wants to delete and use MongoDB's query to delete the items. However, the backend showed that it is successfully deleted it, but the parent Cart component still render's the items that have been deleted. I wanted to find a way to trigger a re-render a Cart once the item is deleted.

Edit:

The simple data from the url is an array of object like that looks like this:

[{ desc: 'Market', total: 4000, amount: 1 }
{ desc: 'Data', total: 17000, amount: 1 }]

Response in the handleDelete() is the response of axios called, with format like the following:

{data: {…}, status: 200, statusText: "OK", headers: {…}, config: {…}, …}
config: {adapter: ƒ, transformRequest: {…}, transformResponse: {…}, timeout: 0, xsrfCookieName: "XSRF-TOKEN", …}
data: {express: Array(1)}
headers: {date: "Wed, 03 Oct 2018 18:30:02 GMT", etag: "W/"37-QxQ/lxfNH/fWEmtfXYAG1YLiA/E"", x-powered-by: "Express", content-length: "55", vary: "Accept-Encoding", …}
request: XMLHttpRequest {onreadystatechange: ƒ, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
status: 200
statusText: "OK"
__proto__: Object

From response.data , i get express: Array(1) so from the response.data.express , I am actually getting the array of data

After @Code-Apprentice's edit. Now the problem is that, the second axios called did not invoke get invoke after the first remove call is done. i tried to print out what I got back from that that second axios and it is the same data as the what are first rendered.

To trigger a rerender of any component, you can call this.setState() . In this case, you need to remove the deleted item from cartData and call something like this.setState({cartData: updatedData}); . (Be sure you map over cartData instead of testData in render() .)

In order to do this, you need a callback function that is passed as a prop from Cart to Item . Call it onDelete() , for example. This function is responsible to implement the logic described in the previous paragraph. You can implement this in one of two ways:

  1. Fetch the data from the back end. This will give you exactly the list of items that are in your database.

  2. Remove the deleted item from this.state.cardData . This is quicker because you don't have to make a network request, but it risks deleting the wrong item so that the component's state is not the same as the data in the backend database.

Addendum:

In your current version, you attempt to pass a callback with this line of code:

delete={this.handleDelete(item.desc)}

The problem is that this calls this.handleDelete() immediately as you map over each item in your cart data. So every item is deleted. Instead, you need to create a function which will do this when the button is clicked:

delete={() => this.handleDelete(item.desc)}

So basically once you delete an item you need update your state in your cart component. To do that you can again fetch the data or delete just that item from cart state. In the following snippet, handleDelete will splice the item from cartData and set it to state which will trigger component render.

<Item
    key={index}
    desc={item.desc}
    amount={item.amount}
    total={item.total}
    onDelete={this.handleDelete}
/>

handleClick(e){
    e.preventDefault()
    axios({method: "post", url: "/remove", data:{name: this.props.desc}}).then(() => {this.props.handleDelete(this.props.desc)})
}

You should create a function in the parent component that take an item id and this function will be responsible for making the delete request and after the response return remove the deleted item from the array and setState for the parent. Send this function to each item on parent render method. In the child component when the user click the delete button will invoke the passed function from the parent and pass the function the id.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM