I seem to be struggling with what should be a simple issue regarding setting State. I'm learning React so I did this across several steps. Initially I setup my components so they are setting fields using props (e.g. this.propsoverview[0].ProfileImg) which was handed down State from Overview Pane, which was initially set using a this.SetState call in componentWillMount where data was pulled from a static file.
Next I worked on adding a function to pull the data in a more dynamic way (i.e. getPatient function). I'm calling this function from the componentWillMount and I am able to do a console.log to output the JSON as a string (using JSON.stringify). So I know the JSON is being returned.
Where I'm struggling is setting the State in componnetWillMount using the returned JSON from my getPatient call. I get the error Uncaught (in prmise) Type Error: Cannot read property 'setState' of undefined on the 4th line of my code (i.e. this.setState...)
Below is my code...
componentWillMount() {
getPatient().then(function(result) {
//console.log(JSON.stringify(result));
this.setState({PATIENT: result})
})
function getPatient() {
const urlGetPatient = 'url_that_gets_patient_here';
return fetch(urlGetPatient).then(function(response) {
return response.json();
}).then(function(json) {
return json;
});
}
render() {
return (
<App>
…
<OverviewPane overview={this.state.PATIENT} />
…
</App>
}
class OverviewPane extends React.Component {
constructor(props) {
super(props);
autoBind(this);
}
render () {
return (
<table>
<tbody>
<tr>
<td><Image src={this.props.overview[0].ProfileImg}/></td>
</tr>
</tbody>
</table>
);
}
}
Any help would really be appreciated.
You're getting cannot call setState of undefined because you're using a regular function as a callback instead of an arrow function (() => {}). When using a regular function, the this parameter (context) is set to the calling function, and not what you might think (the so-called lexical this).
Change your .then callback to an arrow function, and you should be good:
componentWillMount() {
getPatient().then((result) => {
this.setState({PATIENT: result});
})
}
The this in your code is bound within the promise, you need to bind this in order to get reacts this scope.
getPatient().then(function(result) {
//console.log(JSON.stringify(result));
this.setState({PATIENT: result})
}.bind(this))
Related
I have a simple program that consumes IMDB api, I'm getting the result, but it was shown as error because the result is not a structured json.
MovieService.ts
export class MovieService {
constructor(private http:HttpClient) { }
getMovie(movie:string){
return this.http.get(this.generateURL(movie));
}
private generateURL(movie:string){
return "https://v2.sg.media-imdb.com/suggests/titles/"+movie.charAt(0)+"/"+movie+".json?callback=imdb$"+movie;
}
}
addmovie.component.ts
private _filterMovies(value: string) {
this.movieService.getMovie(value).subscribe(
movies => {
console.log(movies);
return movies;
}
);
}
ngOnInit() {
this.addMovieForm.get('movie').valueChanges.subscribe(val => {
this._filterMovies(val)
});
}
I'm getting error like
the response is of bad json. How can I format the json upon receiving? How to solve this? Any leads would be helpful.
The result is not JSON, but rather JSONP. It is essentially returning you a script that is trying to execute the callback method specified.
Instead of http.get() you should call http.jsonp(url, "imbdIgnoresThisParam").
However, according to this answer, the callback query string parameter is ignored by IMDB. The answer suggests dynamically creating the expected callback function, whose name contains the title for which you are searching. In that callback you could do a few different things.
Use the closure to call / set something in your MovieService. This will result in your call to the API throwing an error, as the Angular framework's callback will not be called as expect. You could ignore the error.
Try to call the expected Angular callback, ng_jsonp_callback_<idx>. This will prevent the API call from throwing, but it may not be reliable. The callback name is dynamic and increments with each jsonp() call. You could try to track the number of jsonp() calls in your app. And of course, the framework may change and break this solution. Concurrent calls to getMovie() may break, as the next one may step on the previous callback on the window. Use with caution!
In typescript, your getMovie() function and related helpers might look like so:
private imdbData: any = null;
private jsonpIdx = 0;
private setImdb(json: any) {
this.imdbData = json;
// or do whatever you need with this
}
getMovie(movie:string) {
// dynamically create the callback on the window.
let funcName = `imdb$${movie}`;
window[funcName] = (json: any) => {
// use the closure
this.setImdbData(json);
// or try to call the callback that Angular is expecting.
window[`ng_jsonp_callback_${this.jsonpIdx++}`](json);
}
// go get your data!
let url = this.generateURL(movie)
return this.http.jsonp(url, "ignored").subscribe((json) => {
// this happens if you successfully trigger the angular callback
console.log(json);
}, (err) => {
// this happens if the angular callback isn't called
console.log(this.imdbData); // set in closure!
});
}
Edit for Angular 4
For Angular 4, it looks like you will need to import the JsonpModule along with the HttpModule. Then, you'd inject jsonp just like you'd inject http into your service. The call to IMDB becomes this.jsop.request(url).subscribe(...) and your dynamic callback name needs to change, too.
window[funcName] = (json: any) => {
// or try to call the callback that Angular is expecting.
window["__ng_jsonp__"][`__req${this.jsonpIdx++}`]["finished"](json);
}
I don't have an Angular 5 or 6 project immediately set up, so hard to say if there are any differences with the callback in those versions.
Sort of a hack, but hope it helps!
How should I parse this using lifecycle methods?
{"blocks":[{
"key":"33du7",
"text":"Hello there!",
"type":"unstyled",
"depth":0,
"inlineStyleRanges":[],
"entityRanges":[],
"data":{}}],
"entityMap":{}
}
I want to render the text in my component but I don't know why it throws undefined error. How should I call it?
This is my component:
class Blog extends Component{
constructor(props){
super(props);
this.blogContent = props.blogContent;
this.blogId = props.blogId;
this.handleRemoveBlog = this.handleRemoveBlog.bind(this);
this.state = {
blog__: '',
};
}
handleRemoveBlog(blogId){
this.props.removeBlog(blogId);
}
This is my lifecycle method , I would use this.setState but first of all it's giving undefined in console.
componentWillMount(){
this.state.blog__ = JSON.parse(this.blogContent);
console.log(this.state.blog__.text); // this gives undefined
}
This is the render part..
The data is coming from Firebase.
And {this.blogcontent} gives that json string that I previously mentioned.
render(props) {
return(
<div className = "blog header">
<p>{this.blog__.text}</p>
</div>
);
}
}
Blog.proptypes = {
blogContent: Proptypes.string
}
This would mostly depend on where you are getting this object from. If it is fetched over the network then the best place to pass it is in the componentDidMount. The reason for this is that the alternative lifecyle method (componentWillMount) does not guarantee a re-render of your component since it does not wait for async actions to finish execution before passing control down to your render method. Hence componentDidMount is best because as soon as new props are received or state is changed it will trigger a re-render. However, if this object is pulled from within the application then chances are, it will work just fine even if pulled within componentWillMount. This is because that operation would be much quicker, so much that control would be passed down to the render method with the new props. This is not guaranteed especially if you want to set state in the process (setting state is also async, so control might execute the rest of the code before all the required data is received).
In short, pass this to componentDidMount and in your render function, before accessing this prop, make sure that it exists. That is, instead of
render() {
return <div>{this.props.theObject.blocks[0].key}</div>
}
rather do:
render() {
return <div>{this.props.theObject && this.props.theObject.blocks[0].key}</div>
}
This is how you would do it (assuming you are getting the file over the network using axios)
componentDidMount() {
axios.get('url/to/the/file')
.then(fileData => this.setState({
data: fileData
});
}
render() {
// return whatever you want here and setting the inner html to what the state holds
}
You should not modify the state using
this.state.blog__ = JSON.parse(this.blogContent);
The proper way to do it is using the this.setState() method:
this.setState({blog__: JSON.parse(this.blogContent)})
Then, to ensure that the component will be re-rendered, use the method shouldComponentUpdate():
shouldComponentUpdate(nextProps,nextState) {
if(nextState != this.state) {
this.forceUpdate()
}
}
Take a look at the State and Lifecycle docs.
Other point: Use componentDidMount() instead of componentWillMount(), because it will get deprecated in the future.
Atention: The setState() is an asynchronous method. So, it won't instant update your state.
Use this.setState({}) in your componentWillMount function instead assign the data to the variable. Also I recommend to use componentDidMount instead of componentWillMount because it's getting deprecated in the future.
componentDidMount(){
let text = JSON.parse( this.blogContent );
this.setState({blog__: text });
}
Edit: Only use setState in componentDidMount according to #brandNew comment
I'm trying to directly bind an array to a Polymer 2 component template. It works at first but then it doesn't detect changes. I'm using this.push method in order to allow Polymer to detect the changes, but it's not working. Binding to a dom-repeat template it's OK.
Here you can find a Plunker: http://plnkr.co/edit/MWO7i7m3GB5b7Eqri1yX?p=preview
Is it possible to do what I'm trying? What am I doing wrong?
Thank you for your help
No it's not possible to bind to an array like this.
[[json(data)]] if data is an array, it won't work.
The same way for the observers :
static get observers(){
return [
'_dataChanged(data)'
]
}
This won't work.
On a direct binding, the only way for it to work is to change the complete array value with another one.
class ParentElement extends Polymer.Element {
static get is() { return "parent-element"; }
static get properties(){
return {
data: {
type: Array,
notify: true,
value: function(){
return [{name: 'first'}]
}
}
}
}
constructor() {
super();
}
json(data){
return JSON.stringify(data);
}
addChild(){
this.data = [{name: 'first'},{name: 'second'}];
}
}
customElements.define(ParentElement.is, ParentElement);
In this case above, you can see that the data array is completely replaced and it will work because it's the data object that changed.
In your case you change the content of the array, then the binding won't work.
If you want to see something, in you plunker you can change the HTML part with :
[[json(data.*)]]
Then you will see the polymer binding object that changed.
But instead of binding an array like you did, the best is to use an observer and do our action in the function :
static get observers(){
return [
'_dataChanged(data.splices)'
]
}
_dataChanged(value){
//work to do here
}
For more details you can check the polymer doc here
I have the following service in Angular2:
#Injectable()
export class MyService{
private myServiceUrl= ....
constructor(private http: Http) { }
getService(): Promise<MyObject> {
return this.http.get(this.myServiceUrl).map(response => response.json())
.toPromise();
}
}
And I have this function in one of my components:
myFunction(): any{
let toReturn: any;
this.myService.getService().then(result => toReturn = result);
console.log(toReturn);
return toReturn;
}
Basically, as you can see, I want to store in toReturn, the objects that it is returned by the promise of myService. I am looking to the rest call I have, and I am getting the proper json, but when I am trying to store it in the internal variable, I get undefinied in toReturn.
However, if I try this:
this.myService.getService().then(result=> console.log(result));
I am able to see the json I want.
I can do a function like this:
getService(address: string) {
this.myService.getService().then(result=> this.result= result);
}
But I prefer to make my function to return an object. What am I doing wrong?
Remember that getService is asynchronous, so when your code reaches the line
return toReturn;
The toReturn variable has not yet received the data coming from the server.
The cleanest way to deal with this is to return the promise itself, so that the calling code will extract the data when it arrives from the server:
myFunction(): Promise<MyObject>
{
//return the promise that will output data in the future
return this.myService.getService();
}
The calling code can access the data this way:
myFunction().then( result =>
this.result= result
)
You can even remove a step by getting rid of myFunction because it's just a thin wrapper around getService(). Instead the calling code can simply do:
this.myService.getService().then(result => this.result = result)
And that's it.
Addendum to address your comments:
You must understand two concepts about asynchronous ops and promises:
A promise is executed in a different thread, so the lines after the call to getService() are executed before the data arrives. That's why console.log() doesn't show you data at that point. That's also why trying to return the result there doesn't work
the only place in code where you can capture the result of a promise is .then(). There you can do whatever you want with the result, including storing it in any variable. Just remember point 1 above, and don't expect to have access to the result on the very next line because then() occurs later in time on a different thread.
assign the json that comes from that service (which returns a
promise), to an internal variable that I defined in the line above, it
is not working. How can store it to an internal variable and return it
You cannot.
I'm using the react-router and navigate to a component that gets an ID in the URL and has to use this ID to get data from the server with the help of an action.
At the moment I'm calling the action creator in the componentWillMount hook.
This works so far, but brings a problem.
In the render method I have to check, if myData really exists with all its attributes, before I can really render.
#connect(state => {myData: state.etc.myData})
export default class extends React.Component {
componentWillMount = () => {
this.props.dispatch(
ActionCreators.getData(this.props.params.id)
)
}
render() {
if (this.props.myData.hasNotLoaded) return <br/>
...
}
}
Is there another way to get data into the store before rendering without manual checks?
You can subscribe to router's onEnter hook and dispatch actions from where.
const store = configureStore()
const routing = (<Router>
<IndexRoute onEnter={()=>store.dispatch(myLoadDataActionCreator())}/>
</Router>)
So you can avoid setState from previous answer and don't tie up component with redux.
You should create a call back, for example:
_onChange() {
this.setState(myStore.getData());
}
Then in the following react functions do the following:
componentDidMount() {
myStore.addChangeListener(this._onChange);
},
componentWillUnmount() {
myStore.removeChangeListener(this._onChange);
}
I assume you're using the mixins for the react-router, if not, take a look at the docs for it, they have some useful functions that are worth looking at.
I don't think you will need that if logic in the render() method, react will handle that with the virtual dom management and know when to load it and the data.