Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React is not updating when Redux state is changed below the first level

I have my state in Redux and am using the react-redux connector to tie it into my React application. During the initial run up where the state is set, the connection works great. However, when I update a portion of the state in my reducer, it is not being read as changed by React so the component does not render again. When I do some other action that forces a render, the state has, actually, been changed.

It appears to have to do with the state not being recognized as having changed and, in the wrapWithConnect method of react-redux, the storeChanged and propsChanged both evaluate to false after the initial run through. A clue might be that react-redux compares at a shallow level in those checks.

The question is "what do I need to do to get React to display changes to my state when the state changes in Redux"?

The relevant part of the state is below

let thisOrder = Immutable.fromJS( {
"projectNumber": '',
"orderHeader": {
    "order_id": "",
    "firstname": "",
    "lastname": "",
    "address1": "",
),
"orderProducts": [],
"lastOperation": ''} );

The section that gets updated is orderProducts, an array of products that basically looks like

  orderProducts:[{productSKU:'Y54345',productDesc:'What it is',price:"4.60",qty:2},
{productSKU:'Y54345',productDesc:'What what is',price:"9.00",qty:1}]

When updating qty or price in the reducer below (called by an action) the resultant newState has the changes

 case OrderConstants.ORDER_PRODUCT_UPDATE:{

 let productList = updateProductList( action.updateProduct, state.get(
 'orderProducts' ))

 let newState = state.update( 'orderProducts',
 productList).set('lastOperation', action.type);

  return newState;}

And finally, the controlling React component that then sends the props downstream

    render: function () {
        return (
            <div>
                <ProductList updateOrder={this.updateOrder()} products={this.props.products}/>

                    {this.submitContinueButton()} {this.submitButton()}
                    {this.cancelButton()}
                </div>
            </div>
        )
    }
} );
// connect to Redux store
var mapStateToProps = function ( state ) {
    return { ocoStore: state.toJS(),
            products: state.get('orderProducts')};
};

export default connect( mapStateToProps )( OrderProducts );

All of the above is trimmed to the relevant sections. Note that the code works to display the product information on the lower components on the initial load up. It's when updating price or qty that the refresh does not occur.

like image 899
Doug Johnson-Cookloose Avatar asked Nov 09 '22 23:11

Doug Johnson-Cookloose


1 Answers

I have replicated your solution using JSBIN and I managed to get it to work so I thought I would share in case it helps you in any way.

Below is the code as seen in JSBIN. It is based of the code snippets in your question. Note that you didn't provide the code for your updateProductList method and the code for the ProductList component so I had to create my own.

const initialState = Immutable.fromJS({
  "projectNumber": '',
  "orderHeader": {
    "order_id": "",
    "firstname": "",
    "lastname": "",
    "address1": "",
  },
  "orderProducts": [
    { 
      productSKU: 'Y54345',
      productDesc: 'What it is',
      price: "4.60",
      qty:2
    },
    {
      productSKU: 'Y54346',
      productDesc: 'What what is',
      price:"9.00",
      qty:1
    }
  ],
  "lastOperation": ''
});


const updateProductList = (updateProduct, productList) => {
  return productList.map(product => {
    if (product.get('productSKU') === updateProduct.productSKU) {
      return Immutable.fromJS(updateProduct);
    }
    return product;
  });
};

const order = (state = initialState, action) => {
  switch (action.type) {
    case 'ORDER_PRODUCT_UPDATE':
      let updatedProductList = updateProductList(action.updateProduct,state.get('orderProducts'));

      const newState = state.set( 'orderProducts', updatedProductList).set('lastOperation', action.type);

      return newState;
    default:
      return state;
  }
};

const { combineReducers } = Redux;
const orderApp = combineReducers({ order });

const { Component } = React;
const { Provider, connect } = ReactRedux;

const ProductList = ({
  products
}) => {
  return (<ul>
          {products.map(product => {
            return (
             <li key={product.get('productSKU')}>
               {product.get('productSKU')} - {product.get('productDesc')} - {product.get('qty')}
             </li>
            );
          }
          )}
        </ul>);
};

const OrderProducts = ({
  ocoStore,
  products,
  dispatch
}) => (
              <div>
                <ProductList products={products} />
                <a href='#'
                   onClick={e => {
                     e.preventDefault();
                     dispatch({
                       type: 'ORDER_PRODUCT_UPDATE',
                       updateProduct: {
                         productSKU: 'Y54345',
                         productDesc: 'What it is',
                         price: "4.60",
                         qty: 8
                       }
                    });
                 }}>Update Product Y54345</a>
            </div>
)

const mapStateToProps = ( state ) => {
    return { 
      ocoStore: state.order.toJS(),
      products: state.order.get('orderProducts')
    };
};

const OrderApp = connect(mapStateToProps)(OrderProducts);

const { createStore } = Redux;

ReactDOM.render(
  <Provider store={createStore(orderApp)}>
    <OrderApp />
  </Provider>,
  document.getElementById('root')
);

JSBIN: http://jsbin.com/qabezuzare

The part in my code that differs is the following:

  const newState = state.set( 'orderProducts', updatedProductList).set('lastOperation', action.type);

This is because my updateProductList method returns a new Immutable.List.

I hope looking at the working solution will help you troubleshoot your problem.

like image 119
DDA Avatar answered Nov 14 '22 23:11

DDA