-1

I am confused with how the react refs works. The issue for me is, whenever I change the input select value, update_cart function is called.

I then call actions to set the value using relevant APIs.

However, currently, whenever I change the value, the whole component refreshes and the refs value are set to undefined.

What am I doing wrong?

Please note I have included only relevant codes.

    /** @jsx React.DOM */
    'use strict'

    var React = require('react')
    var connect = require("react-redux").connect
    var moment = require('moment')
    var actions=require("../actions");
    var Calendar=require("./calendar");
    var utils=require("../utils");
    var CartChangeQty = require('./cart_change_qty')
    var Table = require('react-bootstrap').Table
    var Input = require('react-bootstrap').Input
    var Register = require('./register')
    var GroceryCheckout = React.createClass({
        getInitialState: function() {
            return {provinces: [], postal_codes: []};
        },

        render: function() {
            console.log("GroceryCheckout.render");
            var day_slots=[];
            if (this.props.grocery_cart) {
                var delivery_date=this.props.grocery_cart.delivery_date;
                if (!delivery_date) delivery_date=this.props.grocery_cart.delivery_slots[0][0];
                _.each(this.props.grocery_cart.delivery_slots,function(obj) {
                    if (obj[0]==delivery_date) {
                        day_slots=obj[1];
                    }
                }.bind(this));
                console.log("###### day_slots",day_slots);
            }
            return <div className="plr-grocery-checkout">
                <a className="plr-anchor" id="checkout"></a>
                <h2>Grocery Checkout</h2>
                {function() {
                    if (!this.props.grocery_cart) return <p>Your grocery cart is empty.</p>;
                    if (!this.props.user_data) {
                        return <div>
                            <p>
                                Is this your first time ordering? <input type="radio" name="first_time" ref="first_time_yes" onClick={this.onchange_first_time.bind(this,true)}/> Yes <input type="radio" name="first_time" ref="first_time_no" onClick={this.onchange_first_time.bind(this,false)}/> No
                            </p>
                            {function() {
                                if (this.state.first_time==true) {
                                    return <Register/>
                                } else if (this.state.first_time==false) {
                                    return ///something
                    } else {
                        return <div>
                            ///something
                             <div>
                            <h4><i className="glyphicon glyphicon-home"> </i> Delivery Address</h4>
                            <Input type="select" onChange={this.update_cart} ref="ship_address" style={{width:'auto',padding:'inherit',height:'auto'}}>
                                {this.props.user_data.contact_id.addresses.map(function(obj) {
                                    return <option key={obj.id} value={obj.id}>{obj.address}</option>
                                })}
                            </Input>

                            <h4><i className="glyphicon glyphicon-calendar "> </i> Please select your preferred delivery time slot:</h4>
                            <Calendar />

                                <div className="form-group">
                                    <label className="col-sm-2 control-label">Payment Method</label>
                                    <div className="col-sm-6">
                                        <Input type="select" onChange={this.update_cart} ref="pay_method" style={{width:'auto',padding:'inherit',height:'auto'}}>
                                            {this.props.grocery_cart.payment_methods.map(function(obj) {
                                                console.log("********************** payment method",obj.name)
                                                return <option key={obj.id} value={obj.id}>{obj.name}</option>
                                            }.bind(this))}
                                        </Input>
                                    </div>
                                </div>
                                <div className="form-group">
                                    <label className="col-sm-2 control-label">Payment Amount</label>
                                    <div className="col-sm-6">
                                        <p>{this.props.grocery_cart.amount_total} ฿</p>
                                    </div>
                                </div>
                            </div>
                            <hr/>
                            <h4><i className=" glyphicon glyphicon-list-alt"> </i>  Other Information</h4>
                            <div className="form-horizontal">
                                <div className="form-group">
                                    <label className="col-sm-2 control-label">Return Foam Box</label>
                                    <div className="col-sm-6">
                                        <input type="checkbox"  onChange={this.update_cart}/>
                                        <span style={{margin:10}}>For this delivery, would you like us to take back the foam box for recycling?</span>
                                    </div>
                                </div>
                            </div>
                            <div className="form-horizontal">
                                <div className="form-group">
                                    <label className="col-sm-2 control-label">No Call</label>
                                    <div className="col-sm-6">
                                        <input type="checkbox" onChange={this.update_cart}/>
                                        <span style={{margin:10}}>For this delivery, please do NOT call me one hour before delivery to re-confirm unless delayed</span>
                                    </div>
                                </div>
                            </div>
                            <button className="btn btn-lg btn-primary" onClick={this.send_order} disabled={this.props.grocery_cart.amount_total<1500?true:false}><span className="glyphicon glyphicon-ok"></span> Send Order</button>
                            {this.props.sending_grocery_order?<span><img src="/static/img/spinner.gif"/> Sending order...</span>:null}
                            {function() {
                                if (this.props.grocery_cart.amount_total>=1500) return;
                                return <span className="plr-warning" style={{marginLeft:"20px"}}>Min. order: 1500 ฿!</span>
                            }.bind(this)()}
                        </div>
                    }
                }.bind(this)()}
            </div>
        },

        onchange_first_time: function(value) {
            console.log("GroceryCheckout.onchange_first_time",value);
            this.setState({first_time: value});
        },

        update_cart: function() {
            console.log("GroceryCheckout.update_cart");
            console.log("this.refs.pay_method.value",this.refs.pay_method.value);
            var vals={
                customer_id: this.props.user_data.contact_id.id,
                ship_address_id: this.refs.ship_address.value||null,
                bill_address_id: this.refs.bill_address.value||null,
                pay_method_id: parseInt(this.refs.pay_method.value),
            };
            this.props.dispatch(actions.grocery_cart_update(vals));
        },



        onchange_qty: function(product_id,qty) {
            console.log("GroceryItem.onchange_qty",product_id,qty);
            this.props.dispatch(actions.grocery_cart_set_qty(product_id,qty));
        },


    })

    var select=function(state) {
        return {
            grocery_cart: state.grocery_cart,
            grocery_cart_loading: state.grocery_cart_loading,
            user_data: state.user_data,
            user_data_loading: state.user_data_loading,
            sending_grocery_order: state.sending_grocery_order,
        }
    }

    module.exports=connect(select)(GroceryCheckout);
Chris Martin
  • 30,334
  • 10
  • 78
  • 137
Sijan Shrestha
  • 2,136
  • 7
  • 26
  • 52
  • is this a babel'ed code? consider posting the original es6 codes (consider using es6 if you are not already) for better viewing. Also, consider break your component into smaller components – Yaobin Then Feb 18 '16 at 04:51
  • Hi there. I am using standard JSX syntax that goes well with React. I posted the original codes. I can post a compiled JS code . I am sorry i am confused cause i am new to this technology and i have not used babel code. – Sijan Shrestha Feb 18 '16 at 06:34
  • Please trim this code down into a [minimal example](http://sscce.org/). – Chris Martin Mar 06 '16 at 10:25

2 Answers2

2

The reason your setup breaks down is probably because:

  • on_change_first_time() includes a setState(), which re-renders your component.
  • update_cart() dispatches an action. Probably this action triggers a new set of props for the component, causing the component to be re-rendered.

In both cases, your refs are probably preserved, but the values are not. Because they are not part of props, nor state. Because the props and state do not include value, react will empty the values upon re-rendering.

It is generally not good practice to read values from input components usings this.refs. In React, refs are for reading and updating DOM, but intended to be used only for stuff that you cannot do through pure react. Examples would be to read the height or width of HTMl components, or to add or remove event listeners to DOM components.

In your case, your update_cart() sends all values to some sort of other function, which presumably stores them somewhere.

I would advise:

  • put all values of all inputs in props, and pass them to the component.
  • in your render function, give all your input components a value={this.props.foo} value or similar.

That way, after your cart is sent of and updated, the component will be re-rendered with the new values.

Optionally, you could include optimistic rendering, by adding:

  • in getInitialState(), copy all your prop values to state values (as initial state of the component).
  • include parameters in input fields like value={this.state.foo}
  • in update_cart(), after your dispatch, add a setState() to update the state to the new input values.
wintvelt
  • 13,855
  • 3
  • 38
  • 43
1

The input i am using is a react bootstrap

<Input type="select" onChange={this.update_cart} ref="ship_address" style={{width:'auto',padding:'inherit',height:'auto'}}>
   {this.props.user_data.contact_id.addresses.map(function(obj) {
       return <option key={obj.id} value={obj.id}>{obj.address}</option>
   })}
</Input>

The problem was whenever the onchange was called the render method was again called. It rerenders because i am calling the set state in here.

One way to fix the issue is to put the input into a different component , where it just re renders that component on change .

Cheers.

Igor Ivancha
  • 3,413
  • 4
  • 30
  • 39
Sijan Shrestha
  • 2,136
  • 7
  • 26
  • 52