React refs do not update between render

22,802

Under the section "Caution" in the react documentation about refs https://facebook.github.io/react/docs/more-about-refs.html

"Never access refs inside of any component's render method - or while any component's render method is even running anywhere in the call stack."

Which is exactly what you're doing.

Instead you should store state about the component in this.state or properties of the component in this.props

Share:
22,802
Charles Haro
Author by

Charles Haro

Updated on October 13, 2020

Comments

  • Charles Haro
    Charles Haro over 3 years

    So I have this component

    var LineItemRowsWrapper = React.createClass({
      current_lineitem_count: 0,
    
      getAjaxData: function(){
        var lineitem_data  = [];
        for(var i = 0; i < this.current_lineitem_count; i++){
            var data = this.refs['lineitem_'+i].getAjaxData();
    
            lineitem_data.push(data)
        }
        return lineitem_data;
      },
    
      getLineitems: function(){
        var self = this;
    
        var lineitem_components = [];
        this.current_lineitem_count = 0;
        if(this.props.shoot){
          var preview = this.props.preview;
    
          var lineitems = this.props.shoot.get_lineitems();
    
    
          lineitem_components = lineitems.map(function (item, index) {
              var ref_str = 'lineitem_'+self.current_lineitem_count;
              self.current_lineitem_count++;
    
              return (
                <LineItemRow item={item} key={index} ref={ref_str} preview={preview} onChange={self.props.onChange} />
              )
            });
          }
    
        return lineitem_components;
      },
    
      render: function() {
        var lineitems = this.getLineitems();
        return (
          <div>
            {lineitems}
          </div>
        )
      }
    })
    

    the first time lineitems are rendered the refs work like expected. But if I add a lineitem to this.props.shoot the refs object of this component does not change.

    So for example say I had an array of 3 lineitems

     [i1,i2,i3]
    

    this.refs would be

     {lineitem_0:{}, lineitem_1:{}, lineitem_2:{}}
    

    and when I update my lineitem array to be

     [i1,i2,i3,i4]
    

    this.refs does not change, it will still be

     {lineitem_0:{}, lineitem_1:{}, lineitem_2:{}}
    

    why doesn't the refs object update between renders? The LineItemRow components update properly so I know its not something wrong on that front. Any insights would be much appreciated!

    ____Edit____ (requested to add more code for context)

    var DocumentContent = React.createClass({
      contextTypes: {
        router: React.PropTypes.func.isRequired
      },
    
      getParams: function(){
        return this.context.router.getCurrentParams()
      },
    
      getInitialState: function() {
        return {
          shoot: ShootStore.get_shoot(this.getParams().shoot_id),
        }
      },
    
      componentWillMount: function() {  
        ShootStore.bind( 'change', this.onStoreUpdate );
      },
    
      componentWillUnmount: function() {  
        ShootStore.unbind( 'change', this.onStoreUpdate );
      },
    
      onStoreUpdate: function(){
        this.setState(this.getInitialState());
      },
    
    
    
      addLineItem: function() {
          ShootActions.create_lineitem(this.state.shoot.id);
      },
    
    
      update_shoot_timeout: null,
    
      update_shoot:function(){
        var self = this;
        window.clearTimeout(this.update_shoot_timeout)
        this.update_shoot_timeout = window.setTimeout(function(){
    
            var lineitem_data = self.refs.lineitems.getAjaxData()
    
            if(self.props.shoot){
                ShootActions.update_shoot(self.state.shoot.id, lineitem_data )
            }
        }, 500)
      },
    
    
      render: function() {
    
        var shoot = this.state.shoot;
        return (
            <div className='document__content'>
                <div className='row'>
    
    
                <div className='document__expenses'>
                    <h3 className='lineitem__title'> Expenses </h3>
                    <LineItemRowsWrapper shoot={shoot} onChange={this.update_shoot} ref='lineitems'/>
    
                </div>
                <button onClick={this.addLineItem} className="btn-small btn-positive">   
                           + Add Expense
                        </button>  
    
    
    
    
            </div>
        );
      } 
    })
    
  • dannyshaw
    dannyshaw about 9 years
    I think the wrapper is a bit redundant after this actually.