componentInstance.setState undefined in 0.11.0 - javascript

I have the following component in a file called DashboardPosition.js:
var DashboardPosition = React.createClass({
getInitialState: function() {
return {
items: []
};
},
render: function() {
return (
<div className="well well-sm dashboard-item">
This component has {this.state.items.length} items
</div>
);
}
});
I then render this with no problems in my page like so:
var dashboardPosition = <DashboardPosition />;
React.renderComponent(dashboardPosition, document.querySelector("#position"));
But when I try the following:
var dashboardPosition = <DashboardPosition />;
React.renderComponent(dashboardPosition, document.querySelector("#position"));
dashboardPosition.setState({
items: [{name: "AMD"}, {name: "LIQ"}, {name: "ELEC"}]
});
I get the following error:
Uncaught TypeError: undefined is not a function
dashboardPosition.setState
This seems to be only occurring in v0.11.0. I've tried it in v0.10.0 and it works fine. Possible bug or have I missed something?

This was a change that started in 0.10.0 by giving you a warning in the development build, and in 0.11.0 is now a "breaking change".
When you make an instance of a component, what's returned is a descriptor, which is just whatever react needs to render that component. Previously to 0.10.0 this happened to be the actual virtual dom nodes, which lead to a lot of antipatterns and removed potential from react being able to optimize certain things. You can no longer do anything with the returned values except:
return them from a render function
use the cloneWithProps add on to make an updated copy
If you need to setState on a DashboardPosition component, you need to do it from within DashboardPosition.
If it doesn't make sense to do from within DashboardPosition, you should be passing items as a prop, e.g. <DashboardPosition items={items} />, and using this.props.items instead of this.state.items.

Related

Use props instead of state

I know if you want to alter something within a component itself you use state, and when the emit is external then u use props to receive it. But today I stumped across this example
var Label = React.createClass({
handleClick: function(){
console.log("Click");
this.props.children = "Text After Click";
this.setState({liked: false});
},
render: function () {
console.log("Render");
return (
<p ref="p" onClick={this.handleClick}>{this.props.children}</p>
);
}
});
Used props instead of state to change the text a value of a button. I'm confused now. The link to the source is here http://myshareoftech.com/2013/12/unit-testing-react-dot-js-with-jasmine-and-karma.html
I don't know about the source but when i tried above code it throws this error:
Uncaught TypeError: Cannot assign to read only property 'children' of object #<Object>.
It should not work because the basic property of the props is, as per DOC:
Props are Read-Only, Whether you declare a component as a function or a class, it must never modify its own props. All React components must act like pure functions with respect to their props.
Check the fiddle for error: https://jsfiddle.net/pjnp6yza/
Reference: https://facebook.github.io/react/docs/components-and-props.html#props-are-read-only

Pass this.refs as property in jsx in React.js

I'm trying to pass 1 node as a property of another React component like this:
render: function() {
return (
<div>
<div ref='statusCircle'></div>
<Popover trigger={ this.refs.statusCircle }></Popover>
</div>);
);
}
But in the Popover, this.props.trigger is NULL.
Is there anything wrong with my code?
How can I pass a node-ref to another React component as property?
You've misunderstood the component lifecycle in React.
See here: Link
See this fiddle. https://jsfiddle.net/uzvoruf7/
Open your console, inspect the code for the "Composite" component, and see the lifecycle hooks.
var Popover = React.createClass({
render: function() {
return (<div>This is a pop-over</div>);
}
});
var Composite = React.createClass({
componentDidMount: function() {
console.log(this.refs.statusCircle); //ok, exists.
},
render: function() {
console.log(this.refs.statusCircle); //doesn't exist, sorry.
return (
<div>
<div ref='statusCircle'></div>
<Popover trigger={this.refs.statusCircle}></Popover>
</div>
);
}
});
ReactDOM.render(
<Composite />,
document.getElementById('container')
);
"refs" come alive once the DOM has been rendered.
Therefore, it follows that inside that return statement, the dom has not been rendered yet, hence the reference is null (or all references are null so to speak).
However, inside componentDidMount, you can see that your references are available just as one would expect.
This is a common error: a possible code smell that needs refactoring. Usually (not always), passing down dom-references is indicative of an imperative thought process and is not the React way. I would suggest an improvement but I'm not aware of your use-case.
After typing this I realized it's really not an answer to the original question but instead a follow-up to your request for suggestions in your comment above. If you really don't believe it should stay, I'll delete it but it's too big for a comment, sorry.
Reference material
You must be using an old version of React, so this may not look right to you at first, but you could track that information using state in that parent component by doing
ref={ function ( element ) {
// at the top of render() put
// var self = this;
self.setState({
circleWidth: element.offsetWidth,
circleHeight: element.offsetHeight
})
}
Assuming those values change over time, you'll want to add an event listener for that change (which can be added inside that ref setup) and have it run that setState again when you need it to re-render.
As for <Popover trigger= you'll want to do something to the effect of:
<Popover trigger={this.state.circleWidth > 999} />
...where 999 is whatever your trigger value would be. If it returns true, then you render Popover. If not, you destroy it. Both situations would be handled inside Popover but will not need to touch that div
Of course, good practice in React is to do that comparison at the top of render() and place the result into a variable like isTriggered.

Should I wrap all React render code with try{} catch(err){}?

Sometimes React fails silently when running render code. It's then hard to find out where it broke.
I wonder, if I should just put all the code for render function inside try {} catch(err) {alert(err)}?
Is it a good practice or should I catch and debug errors in a different way?
One of the cases it shows no errors is when I send some props variables to a component which are actually undefined and it tries to get properties of that undefined variable, like this:
<FormField form={this.props.form} />
Then, inside FormField component render function there is this code:
var value = this.props.form[this.props.id]
Now, if form prop is undefined, the code in FormField component doesn't show any error, but when I use try/catch, it shows this:
Probably I should use try/catch to debug the errors then fix and add checks, then remove try/catch for production?
Sounds like you could benefit from prop validation. Then you can decide upon types for your props.
React.createClass({
propTypes: {
form: React.PropTypes.object,
id: React.PropTypes.string
}
// ...
})
Then if you don't supply a property of the correct type, it will log an error.
Alternatively, you could supply a default value for the property if that would be more appropriate.
React.createClass({
getDefaultProps: function() {
return {
form: defaultFormProp
};
}
});
Then if you don't supply the prop when you render the component, the default value will be used.

Can't access object property on a React object state, even though it exists. Returns undefined

I'm using AJAX to get some JSON, and then I want to display the value. If I log out he object that contains the value I want to display, I can see the key and the value. However, when I try to access the value directly, I get undefined.
Here is the component that I am stuck on:
var WeatherCard = React.createClass({
getInitialState: function () {
return {};
},
componentDidMount: function() {
var comp = this;
$.get("http://api.openweathermap.org/data/2.5/weather?zip=" + this.props.zipcode + ",us", function(data) {
comp.setState(data);
});
},
render: function() {
// I want to get the value # this.state.main.temp
// this works...
console.log(this.state.main);
// this does not work...
// console.log(this.state.main.temp);
// I want "current temp" to display this.state.main.temp
return (
<div className="well">
<h3>{this.state.name}</h3>
<p>Current Temp: {this.state.main} </p>
<p>Zipcode: {this.props.zipcode}</p>
</div>
);
}
});
Here is the whole plunk.
http://plnkr.co/edit/oo0RgYOzvitDiEw5UuS8?p=info
On first pass, this.state is empty which will render this.state.main.temp as undefined. Either you prefill the state with a correct structured object or wrap in if clauses.
For objects that returns null or undefined React will simply skip rendering it without any warnings or errors, however when you have nested structures that return null or undefined normal JavaScript behaviour is employed.
Try setting your initial state using main: [], data: [] rather than an empty object.
I was having the same problems and once I took my AJAX setState and made an empty initial state everything started working fine.
I'm new to React so I can't give you a very good explanation as to why this made a difference. I seem to stumble into all kinds of strange problems.
Hope this helped.

React best way to handle empty object before mount?

I have a React class, and it wants to render an object shown below:
data: {
title: "haha",
description: {
country: "US",
year: "1996"
}
}
But, when React wants to render it, it gives an error.
Uncaught Error: Invariant Violation: receiveComponent(...): Can only update a mounted component
I think the problem is in the getInititalState, I declare my data as an empty object, so when I get my full data object after the timeout, React will try to map my data object to the component, but it gives error.
But one interesting thing is, I have no problem accesing this.props.title.title, but not this.props.title.description.country, it will give undefined
But, when I console.log it, I could see my object. But React cant access it!
My guessing is when React initializing from the empty object, it will only init the virtual DOM with the 1st and 2nd level of the data object.
That the reason, when I try to access this.props.data.data.title is OK but not this.props.data.data.description.country
Below is my code
var BookBox = React.createClass({
getInitialState: function() {
return { data: {} };
},
componentWillMount: function() {
var that = this;
setTimeout(function() {
console.log('timeout');
that.setState({
data: {
title: "haha",
description: {
country: "US",
year: "1996"
}
}
});
}, 2000);
},
render: function() {
return (
<div>
<h1>{this.state.data.title}</h1>
<TestBox title={this.state.data} />
</div>
);
}
});
var TestBox = React.createClass({
render: function() {
console.log(this.props.title);
return (
<div>
<p>{ this.props.title.description.country }</p>
<p>{ this.props.title.title }</p>
</div>
);
}
})
May I know what is the best way to handle this problem? should i init my data object structure in the getInitialState or there is a better way?
I think you are getting Can only update a mounted component error because you are using componentWillMount and settimeout together, but you dont know if the component has been mounted by the time settimeout function fires.
Since you know what your state is beforehand, I think it is best to return your data from getInitialState function.
You can also use componentDidMount instead of componentWillMount function. That way you can be sure the component is mounted when componentDidMount is called.
Any time time you are using asycn functions like settimeout or a xhr call, you should use this.isMounted() in the callback function, to check that the component is still mounted by the time the callback fires.
For example, if you didnt know the state beforehand, you could fire an xhr call in the componentDidMount function, check this.isMounted() in the success callback and setState.
As for the error on <p>{ this.props.title.description.country }</p> line: At initial render this.state.data (BookBox) is an empty object so is this.props.title(TestBox). Accessing empty object's ({ }) title property is undefined. No problem there. Accessing description is also undefined. But accessing undefined's country is error. To avoid this error you can create a description variable: description = this.props.title.description || {} and use <p>{description.country}</p> to make sure your code doesnt break if this.props.title is empty.

Categories