最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React refs do not update between render - Stack Overflow

programmeradmin2浏览0评论

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>
    );
  } 
})

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>
    );
  } 
})
Share Improve this question edited Apr 1, 2015 at 6:36 rene 42.4k78 gold badges121 silver badges165 bronze badges asked Mar 31, 2015 at 6:07 Charles HaroCharles Haro 1,8864 gold badges22 silver badges36 bronze badges 10
  • 1 How and when do you update the lineitem list? What kind of function is passed into this.pros.onChange? Do you have more relevant code? – magnudae Commented Mar 31, 2015 at 8:39
  • 1 I'm using flux and so the lineitem list is updated from a data_store and being passed in by the parent component. I have already checked and this is all working correctly. The onChange function being passed in saves the data to the backend and updates the store when a lineitem is change. The onchange function calls the getAjaxData(). method This is the most relevant part of the code, but i'll post my other code. Everything else is working as it should. I'm pretty sure this is a react thing. Because the {lineitems} component list that is being updated is correct and even has the correct refs. – Charles Haro Commented Mar 31, 2015 at 8:42
  • 1 I read some more about refs on facebooks documentation. I became a little uncertain if it is possible to dynamically add more refs after initial render. I can not see any faults in your code so this is my only theory atm. Keep searching, maybe you can find something here. (if you haven't already tried) – magnudae Commented Mar 31, 2015 at 8:53
  • 1 Yea that's what I thought too. Seems weird that they wouldn't update refs after each render though. I feel like I can't be the only one trying to mess with code this way. Maybe I'm doing something inherently wrong, or frowned upon by the react creators? I found this mattzabriskie.com/blog/react-referencing-dynamic-children but it doesn't seem to be what I need since I'm not using children. – Charles Haro Commented Mar 31, 2015 at 8:56
  • 2 From my impression of what the documentation says about refs, is that it should be avoided. I see you are using refs for the LineItemRowsWrapper. I think you could have passed the ajaxData from the LineItemsRowsWrapper to the onChange sent from from the DocumentContent without using ref. Then you could relieve yourself of that ref. A ref is mostly for accessing data in the html after render. You can use it to access children, but there are other maybe more "Reactier" ways of doing that. It does not solve your problem though (I think). – magnudae Commented Mar 31, 2015 at 9:06
 |  Show 5 more comments

2 Answers 2

Reset to default 14

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

Remove all your refs and iteration to read the data.

The onchange handler you pass all the way down to the LineItem component should be called and passed only the data that changes. (The single LineItem data)

This is then handled back at the component with the state handling (DocumentContent).

Create an action ShootActions.updateLineItem() that updates the relevant line item in the store, which then emits the change and everything renders again.

发布评论

评论列表(0)

  1. 暂无评论