javascript - React: Re-render parent component based on Child component - Stack Overflow

I have one parent ponent, named Cart and another ponent named Items.Basically, I am getting an array o

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

My Cart Component:

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

        this.state={
            cartData:null,
        }

    }

    ponentWillUnmount(){
        _isMounted = false

    }

    ponentDidMount() {
        _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 ponent will get data from the endpoint at Node.js. It is an array of objects and Cart ponent use it to populate the item ponent. The item ponent 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 ponent 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.

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

My Cart Component:

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

        this.state={
            cartData:null,
        }

    }

    ponentWillUnmount(){
        _isMounted = false

    }

    ponentDidMount() {
        _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 ponent will get data from the endpoint at Node.js. It is an array of objects and Cart ponent use it to populate the item ponent. The item ponent 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 ponent 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.

Share Improve this question edited Oct 3, 2018 at 18:33 Spencer Ovetsky asked Oct 3, 2018 at 15:35 Spencer OvetskySpencer Ovetsky 1701 gold badge6 silver badges14 bronze badges 3
  • I'd remend moving the logic out of your Item and into the parent ponent - you can use a callback prop to let the parent ponent know when delete has been clicked. Then the parent ponent can update its own state (which will trigger a re-render). – Joe Clay Commented Oct 3, 2018 at 15:43
  • @JoeClay I am new to React, how would I move the delete logic to the parent ponent – Spencer Ovetsky Commented Oct 3, 2018 at 15:49
  • Please show an example of this.state.cartData that es from the initial request in ponentDidMount. Then what is response in the .then() handler in handleDelete(). You probably need a .then after the call to the /remove route before you make a request to /myCart. – Code-Apprentice Commented Oct 3, 2018 at 18:23
Add a ment  | 

3 Answers 3

Reset to default 2

To trigger a rerender of any ponent, 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 ponent'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)}

You should create a function in the parent ponent 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 ponent when the user click the delete button will invoke the passed function from the parent and pass the function the id.

So basically once you delete an item you need update your state in your cart ponent. 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 ponent 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)})
}

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745232788a4617753.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信