1
votes

I'm using React Router 4.

I have a ShopCoffee component which allows to add items to Cart.

By clicking Cart LINK React renders Cart component.

Without Router (when Cart component is on the same page as main app), Cart works fine.

But another Cart component (which is attached to the router) doesn't recieve props, so the Cart renders as empty. enter image description here

If I click LINK to Cart (cart icon) one more time, it rerenders and shows all items.

enter image description here

So, if I render component like this:

<Cart itemsInCart = {this.state.itemsInCart} deleteItemFromCart = {this.deleteItemFromCart} />

it works correctly,

but when I do this:

const CartRoute = (props) => (<Cart itemsInCart = {this.state.itemsInCart} deleteItemFromCart = {this.deleteItemFromCart} {...props} />);

it works only if I click twice on LINK tag.

Here is the code:

app.jsx

import React from "react";
import ReactDOM from "react-dom";

import Main from "./components/main.component.jsx";
import { createStore } from "redux";
import { Provider } from "react-redux";
import { BrowserRouter, Route } from "react-router-dom";

var app = document.getElementById("app");

function mainAppReducer(state, action) {
    if (!state) return {
        items: []
    }
    switch (action.type) {
        case 'ADD_TO_CART' : console.log('ADD_TO_CART');
                    console.log("action.items == ", action.items);
                    console.log("state==",state);
                    return Object.assign({}, action.items); // state уже содержит все данные об объектах в корзине, поэтому ничего не добавляем

        case 'DELETE_FROM_CART' : console.log('DELETE_FROM_CART');
                    console.log("action.items == ", action.items);
                    console.log("state==",state);
                    return Object.assign({}, action.items); // state уже содержит все данные об объектах в корзине, поэтому ничего не добавляем     
    }
}
const store = createStore(mainAppReducer);
var render = () => ReactDOM.render(
                            <BrowserRouter>
                                <Provider store={store}>
                                    <Route path="/" component = {Main} />
                                </Provider>
                            </BrowserRouter>
                        , app);
store.subscribe(render);
render();

main.component.jsx

import React from "react";
import Header from "./header.component.jsx";
import Footer from "./footer.component.jsx";
import Cart from "./cart.component.jsx";
import Checkout from "./checkout.component.jsx";
import ShopCoffee from "./shop-coffee.component.jsx";
import Rent from "./rent.component.jsx";
import Repair from "./repair.component.jsx";
import Contacts from "./contacts.component.jsx";
import ToCartButton from "./to-cart-button.component.jsx";
import { connect } from "react-redux";
import { Route, Switch } from "react-router-dom";

export class Main extends React.Component {
    constructor(props) {
        super(props);
        this.addNewItemToCart = this.addNewItemToCart.bind(this);
        this.deleteItemFromCart = this.deleteItemFromCart.bind(this);
        this.state = {itemsInCart : []};
    }
    addNewItemToCart(itemsInCart) {
        this.props.dispatch({type : 'ADD_TO_CART',
                             items: itemsInCart});
        this.setState({itemsInCart : itemsInCart});
        console.log("this.state", this.state);
    }

    deleteItemFromCart(i) {
        var itemToDelete = this.state.itemsInCart[i];
        console.log("itemToDelete == ", itemToDelete);
        var itemsLeft = this.state.itemsInCart.filter((x,ind) => ind != i);

        this.props.dispatch({type : 'DELETE_FROM_CART',
                             items: itemsLeft});        
        console.log("itemsLeft == ", itemsLeft);
        this.setState({itemsInCart: itemsLeft});

    }

    getItemsInCart(itemsInCart) {
        return itemsInCart;
    }

    render() {
        const ShopCoffeeRoute = (props) => (<ShopCoffee itemsInCart = {this.state.itemsInCart} addNewItemToCart = {this.addNewItemToCart} {...props} />);
        const CartRoute = (props) => (<Cart itemsInCart = {this.state.itemsInCart} deleteItemFromCart = {this.deleteItemFromCart} {...props} />);
        const CheckoutRoute = (props) => (<Checkout itemsInCart = {this.state.itemsInCart} {...props} />);
        return (
            <main>
                <Header />
                <Switch>
                    <Route exact path="/" render={ShopCoffeeRoute} />
                    <Route path="/rent" component={Rent} />
                    <Route path="/repair" component={Repair} />
                    <Route path="/contacts" component={Contacts} />             
                    <Route path="/checkout" render={CheckoutRoute} />
                    <Route path="/cart" render={CartRoute} />
                </Switch>
                <Cart itemsInCart = {this.state.itemsInCart} deleteItemFromCart = {this.deleteItemFromCart} />
                <ToCartButton itemsInCart = {this.state.itemsInCart} />
                <Footer />
            </main>
        );
    }
}

export default connect((store) => store)(Main);

to-cart-button.component.jsx

import React from 'react';
import { Link } from "react-router-dom";

export default class ToCartButton extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return (
            <Link to="/cart">
            <section className="to-cart-button">
                <div className="to-cart-button__text-container">
                    <p className="to-cart-button__text-container__text">
                        {this.props.itemsInCart.length}
                    </p>
                </div>
            </section>
            </Link>
        );
    }
}

cart.component.jsx

import React from "react";
import { Link } from "react-router-dom";

export default class Cart extends React.Component {
    constructor(props) {
        super(props);
        this.state = {itemsInCart : []};
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.itemsInCart != this.state.itemsInCart) {
            this.setState({itemsInCart : nextProps.itemsInCart});
        }
    }

    deleteItemFromCart(i) {
        var itemsLeft = this.state.itemsInCart.filter((x,ind) => ind != i);
        this.props.deleteItemFromCart(i);
        console.log("itemsLeft === ", itemsLeft);
        this.setState({itemsInCart : itemsLeft});
    }

    render() {
        console.log("Cart /");
        console.log("this.props == ",this.props);
        var imgPath = "img/coffee/"
        var itemsInTable;

        var itemsInCart = this.state.itemsInCart;
        var totalPrice = 0;

        if (!itemsInCart.length) {
            itemsInTable =  (<tr>
                                <td colSpan="5">Ваша корзина пуста</td>
                            </tr>);
        } 
        else {
            totalPrice = 0;
            itemsInTable = (itemsInCart.map((item, i) => {
                                totalPrice += +item.price;
                                console.log("totalPrice==",totalPrice);
                                return (
                                    <tr key={i}>
                                        <td>{item.title}</td>
                                        <td><img src={imgPath + item.image} /></td>
                                        <td>1 шт.</td>
                                        <td>{item.price} руб.</td>
                                        <td><button className="cart__table__delete-button" onClick={this.deleteItemFromCart.bind(this, i)}><i className="fa fa-times"></i></button></td>
                                    </tr>);
                }));
        }

        return (
            <section className="cart">
                <div className="container">
                    <div className="row">
                        <div className="cart__title-container">
                            <h2 className="cart__title-container__title">
                                Ваша корзина
                            </h2>
                        </div>
                    </div>
                    <div className="row">
                        <div className="col-md-12">
                            <table className="cart__table">

                                <tbody>
                                    <tr>
                                        <th colSpan="5">Список товаров</th>
                                    </tr>                           
                                    {itemsInTable}  
                                    <tr>
                                        <td></td>
                                        <td></td>
                                        <td>Итого:</td>
                                        <td>{totalPrice} руб.</td>
                                        <td></td>
                                    </tr>                                                                   
                                </tbody>
                            </table>
                            <div className="cart__next-button-container">
                                <Link to="/checkout"><button className="cart__next-button">Далее >></button></Link>
                            </div>
                        </div>              
                    </div>
                </div>
            </section>
        );
    }
}
2

2 Answers

0
votes

I wanted to expand on alexfrize's response. He references a post that states:

React doesn't call componentWillReceiveProps with initial props during mounting. It only calls this method if some of component's props may update. componentWillReceiveProps() is invoked before a mounted component receives new props.

What does the above mean?

If you have code that is depending on componentWillReceiveProps() to do something (i.e. set state), the method will not fire upon the first Link click when using React Router. In order to get around this, you should call the same (or similar) code you have in componentWillReceiveProps() inside of componentDidMount(). If necessary, include some code in componentDidMount() to make sure that the code is error resistant.