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
2 Answers
Reset to default 14Under 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.