David Johns
David Johns

Reputation: 1714

How to properly update props in redux store?

I have a React App with a shopping cart component. I use Redux to update the shopping cart when clicking on a "Add to cart" button in an item. The problem is, even I update the props in the item component, the prop is not updating concurrently. When I'm checking the props in the component in the Chrom developer tools components tab, I can see the props are updating only when I navigate to another component. However, the cart component never receives the updated prop to populate the cart items. These are the necessary components.

Items component

import React, { Component } from 'react';
import ProductData from './DataSet/ProductData';
import { compose } from 'recompose';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { updateCartList } from '../../store/actions';

class LatestProducts extends Component {

    addItemToCart = (id) => {
        const { cartList, updateCartList } = this.props;
        var items = cartList;
        ProductData.forEach(each => {
            if (each.id === id) {
                items.push(each)
            }
        });
        updateCartList(items);
    }

    render() {

        return (
            <div>
                <div className="itemGridMain">
                    {
                        ProductData.map(product => {
                            return (
                                <div className="itemCard" key={product.id}>
                                    <button onClick={() => this.addItemToCart(product.id)}>Add to cart</button>
                                </div>
                            )
                        })
                    }
                </div>
            </div>
        );
    }
}

function mapStateToProps(state) {
    return {
        cartList: state.cartList,
    };
}

function mapDispatchToProps(dispatch) {
    return bindActionCreators({
        updateCartList: updateCartList,
    }, dispatch);
}

export default compose(connect(mapStateToProps, mapDispatchToProps))(LatestProducts);

Cart component

import React, { Component } from 'react';
import { compose } from 'recompose';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { updateCartList } from '../../store/actions';

class FrontHeader extends Component {

    render() {

        const { cartList } = this.props;

        return (
            <div className="cartList">
                {
                    cartList && cartList.length > 0 && cartList.map(item => {
                        return (
                            <div className="listItem" key={item.id}>
                            </div>
                        )
                    })
                }
            </div>
        );
    }
}

function mapStateToProps(state) {
    return {
        cartList: state.cartList,
    };
}

function mapDispatchToProps(dispatch) {
    return bindActionCreators({
        updateCartList: updateCartList,
    }, dispatch);
}

export default compose(connect(mapStateToProps, mapDispatchToProps))(FrontHeader);

Cart List Reducer

const cartListReducer = (state = [], action) => {
    switch (action.type) {
        case 'UPDATE_CARTLIST':
            return action.payload;
        default:
            return state;
    }
}

export default cartListReducer;

Cart List Index

import cartListReducer from './cartlist';
import { combineReducers } from 'redux';

const allReducers = combineReducers({
    cartList: cartListReducer,
})

export default allReducers;

Redux Actions

export const updateCartList = (newCartList) => {
    return {
        type: 'UPDATE_CARTLIST',
        payload: newCartList,
    }
}

How can I solve this?

Upvotes: 1

Views: 1741

Answers (1)

Drew Reese
Drew Reese

Reputation: 202667

Issue

this.props.cartList is your state and by pushing into that array and saving it back into state you are simply mutating state.

addItemToCart = (id) => {
    const { cartList, updateCartList } = this.props;
    var items = cartList; // <-- state reference
    ProductData.forEach(each => {
        if (each.id === id) {
            items.push(each) // <-- push into state reference
        }
    });
    updateCartList(items); // <-- saved state reference
}

Solution

You should provide a new array object reference for react to pick up the difference since reconciliation uses shallow object equality.

Your addItemToCart should probably just take the item you want added to the cart and move the cart update logic to the reducer.

LatestProducts

class LatestProducts extends Component {

  addItemToCart = (item) => {
    const { updateCartList } = this.props;
    updateCartList(item); // <-- pass item to action creator
  }

  render() {
    return (
      <div>
        <div className="itemGridMain">
          {ProductData.map(product => {
            return (
              <div className="itemCard" key={product.id}>
                <button
                  onClick={() => this.addItemToCart(product)} // <-- pass product/item
                >
                  Add to cart
                </button>
              </div>)
            })
          }
        </div>
      </div>
    );
  }
}

cartListReducer

const cartListReducer = (state = [], action) => {
  switch (action.type) {
    case 'UPDATE_CARTLIST':
      return [...state, action.payload]; // <-- copy state and appen new item

    default:
      return state;
  }
}

Upvotes: 2

Related Questions