0

I'm having an issue updating a value in React. I will also preface that I am a noob at react and still trying to grasp some concepts.

I have a component that increments and decrements a value when clicked. The issue is that it updates all props for all items instead of the one clicked.

For instance I have a list of items, I click on 1 item to update quantity which it does but also updates the quantity of the other items as well. All these items are similar. What also needs to be done is it should count all quantity from all items and output a total which doesn't work either.

This is an image with annotations on what I am trying to accomplish:

http://i.imgur.com/HlhPcym.png

Any help would be greatly appreciated.

Quantity Component:

import React from 'react';
import If from '../utils/helpers';


var QuantityInput = React.createClass({
  getInitialState: function(props) {
    return {
      quantity: this.props.quantity
    };
  },

  handleIncrement: function(e) {
    this.props.handleIncrement(this.props.quantity + 1);
  },

  handleDecrement: function(e) {
    if (this.props.quantity > 1) {
      this.props.handleDecrement(this.props.quantity - 1);
    }
  },

  handleChange: function(e) {
    this.setState({
      quantity: this.props.quantity
    });
  },

  render: function() {
    return (
      <div className="quantity-input">
        <span className="button button--gray controls__quantity" onClick={this.handleDecrement}>-</span>
        <div className="quantity" onChange={this.handleChange}>{this.props.quantity}</div>
        <span className="button button--gray controls__quantity" onClick={this.handleIncrement}>+</span>
      </div>
    );
  }
});

module.exports = QuantityInput;

Products:

import React from 'react';
import If from '../utils/helpers';
import QuantityInput from '../components/quantity.js'


var Product = React.createClass({
  getInitialState: function() {
    return {
      quantity: 0
    }
  },

  handleIncrement: function(e) {
    this.setState({
      quantity: this.state.quantity + 1
    });
  },

  handleDecrement: function(e) {
    if (this.state.quantity > 1) {
      this.setState({
        quantity: this.state.quantity - 1
      });
    }
  },

  handleChange: function(e) {
    var value = e.target.value.replace(/[^0-9]/, '');

    value = (value == '' ? 1 : value);
    value = parseInt(value);

    this.setState({
      quantity: value
    });
  },

  render: function() {
    var content;
    var self = this;
    if (this.props.items.length > 0) {
      this.props.items.map(function(product) {
        var items = product.priceCode.map(function(priceCode) {
          return (
            <div className="list-item" key={priceCode.priceCode_id}>
              <div className="list-info list-info--cart">
                <div className="list-info__venue">
                  <h3 className="event-title">{priceCode.priceCode_title}</h3>
                  <If condition={priceCode.priceCode_passcode}>
                    <input type="text" placeholder="Passcode" />
                  </If>
                  <span className="event-details">{priceCode.priceCode_info}</span>
                </div>
              </div>
              <div className="controls">
                <div className="list-info__price">${priceCode.priceCode_price}</div>
                <QuantityInput quantity={self.state.quantity} handleChange={self.handleChange} handleIncrement={self.handleIncrement} handleDecrement={self.handleDecrement} />
              </div>
            </div>
          )
        });

        content = {items}
      });
    }

    return (
      <div>
        {content}
      </div>
    );
  }
});

var ProductContainer = React.createClass({
  getInitialState: function() {
    return {
      data: [],
      quantity: 0
    }
  },

  componentWillMount: function() {
    this.loadProducts(this.props.url);
  },

  loadProducts: function(url) {
    $.ajax({
      url: url,
      dataType: 'json',
      success: function(data) {
        this.setState({
          data: data
        });
      }.bind(this),
      error: function(xhr, status, err, data) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },

  _hasData: function() {
    var displayedItems = this.state.data.filter(function(product) {
      var match = product.priceCode.filter(function(priceCode) {
        return priceCode.priceCode_title.toLowerCase();
      });

      return (match !== -1);
    }.bind(this));

    return (
      <div>
        <Product items={displayedItems} />
      </div>
    ); 
  }, 

  render: function() {
    if (this.state.data) {
      return (
        <div className="price-code">
          {this._hasData()}
          <div className="subtotal-wrapper">
            <a href="#" className="button button--gray">Clear Selections</a>
            <div className="subtotal">
              Subtotal ({this.state.quantity}):
            </div>
            <a href="#" className="button">Find Tickets</a>
          </div>
        </div>
      )          
    } else {
      return <div>Loading...</div>;
    }

    return false
  }
});

module.exports = ProductContainer;
ethikz
  • 379
  • 4
  • 24

1 Answers1

2

You're QuantityInput components will each receive the same quantity since you're passing them the Product component quantity state.

By the way, you make no distinction between each quantity, it's very confusing. That's one way of doing it :

I will not create a detailed implementation but here is the main points (the [...] indicates that you're code is left unchanged):

ProductContainer

var ProductContainer = React.createClass({
  getInitialState: function() {
    return {
      data: [],
      subTotal: 0
    }
  },

  incrementSubTotal: function() {
    this.setState({
        data: this.state.data,
        subTotal: this.state.subTotal + 1
    });
  },

  decrementSubTotal: function() {
    this.setState({
        data: this.state.data,
        subTotal: this.state.subTotal - 1
    });
  },

  _hasData: function() {
    [...]

    return (
      <div>
        <Product 
            items={displayedItems}
            incrementSubTotal={this.incrementSubTotal}
            decrementSubTotal={this.decrementSubTotal}/>
      </div>
    ); 
  }, 

  render: function() {
    [...]
       Subtotal ({this.state.subTotal}):
    [...]
  }
});

Product

var Product = React.createClass({
  //this component is now stateless

  render: function() {
    [...]

    // the following extract items from within this.props and lets the 'incrementSubTotal' and
    // 'decrementSubTotal' in '...callbacks'. See http://facebook.github.io/react/docs/transferring-props.html
    // for details.
    var {items, ...callbacks} = this.props;
    [...]
        <QuantityInput {...callbacks}/>
    [...]

    return (
      <div>
        {content}
      </div>
    );
  }
});

QuantityInput

var QuantityInput = React.createClass({

  getInitialState: function() {
    return {
      quantity: 0
    }
  },

  handleIncrement: function(e) {
    [...]
    this.props.incrementSubTotal();
  },

  handleDecrement: function(e) {
    [...]
    this.props.decrementSubTotal();
  },

  render: function() {
    return (
      <div className="quantity-input">
        <span className="button button--gray controls__quantity" onClick={this.handleDecrement}>-</span>
        <div className="quantity">{this.state.quantity}</div>
        <span className="button button--gray controls__quantity" onClick={this.handleIncrement}>+</span>
      </div>
    );
  }
});

As you can see I'm passing callbacks to update the state of the Parent component from within child components. It's not a very good practice here. A better approach would be to keep the state of all you inputs on the top level component, acting as a "controller-view" component if you know a little about MVC model. When clicking the "+" or "-" buttons, your application should triggers a event to let the ProductContainer knows that something has changed and it has to update it's state. And it's exactly the role of a the (Flux)[https://facebook.github.io/flux/] architecture and you should definitively take a look a it :)

Pierre Criulanscy
  • 8,726
  • 3
  • 24
  • 38
  • Wow thank you for the detailed enough example! Also thanks for the suggestions on a better way to do things! – ethikz Aug 27 '15 at 16:02
  • You're welcome :) If you decide to try Flux, maybe this another answer could help you : http://stackoverflow.com/questions/28060493/react-flux-and-xhr-routing-caching/32224104#32224104 – Pierre Criulanscy Aug 27 '15 at 16:23
  • Thanks I'll check it out! I do have one question though, I read the post you referenced but I want to make sure I understand it. `{...callbacks}` should be followed by me putting in all the methods I am trying to access correct, like `handleIncrement` etc and keep those on the actual `` or do I just pass those in the `var` definition and leave the component call alone with just `{...callbacks}`? I've tried both but keep getting `this.props...` is not a function – ethikz Aug 27 '15 at 16:29
  • For this part you have to write it exactly as I wrote it. It's a JSX shortcut, you can learn more here : http://facebook.github.io/react/docs/transferring-props.html Please note also that I do not test the actual code that I have posted, it may be containing some typo or other errors, it's just to let you get the idea :) – Pierre Criulanscy Aug 27 '15 at 16:48
  • Ahh ok, yea I read it but was confused. Had to read it a few more times :). Haha ok thanks for the heads up, hopefully I should be able to figure it out from here. Thanks again. – ethikz Aug 27 '15 at 16:51