setState in reactjs inside success function not setting state - reactjs

I am using a ParsePlatform as backend storage and reactjs as front end. I am able to get the parse data using Parse.Query but unable to use the returned values as I do not know how to set the state from the successfull fetching of results. I tried like this way inside componentDidMount()
import React from 'react'
import Parse from 'parse'
class ConferenceInfo extends React.Component {
state={
someData:null
}
componentDidMount(){
this.getConferenceInfo()
}
getConferenceInfo(){
var ConferenceListing = Parse.Object.extend("ConferenceListing");
var cl = new Parse.Query(ConferenceListing);
cl.get("8glBIjeRrC", {
success: function(cl) {
// The object was retrieved successfully.
alert(cl.get("someData")) //it works
//this.setState({someData:cl.get("someData")}) not working
},
error: function(object, error) {
// The object was not retrieved successfully.
// error is a Parse.Error with an error code and message.
}
});
}
render() {
return (
<div>
{this.state.someData} //no result
</div>
)
}
}
export default ConferenceInfo

This is because this is not in the same scope. You are calling it inside success property, function callback.
To get it working we need to bind this.
First Create constructor method.
constructor(props) {
super(props);
// Our new bind method ref
this.change = this.change.bind(this);
}
Then add new method change
change(obj) {
this.setState(obj);
}
Finally your success callback
success: function(cl) {
// ...
this.change({ someData: cl.get("someData") })
},

Related

Assinging value to variable outside class

I am accessing JSON file in ComponentDidMount in class A, i need to access that result outside class and need to use that in Class B
let test;
console.log(test);
class CustomerPage extends React.Component {
componentDidMount(): void {
$.getJSON("/api/LocaleStrings")
.done(results => {
let JsonString = JSON.parse(results);
test = new LocalizedStrings(JsonString);
})
.fail(console.log.bind(console));
}
}
Here, console.log(test) yields undefined.
It seems to me that your console.log(test) gets executed before the AJAX call returns, and at that point it will be uninitialized (undefined). Place your console.log inside the done function.
You could store your AJAX result in your component's state:
class CustomerPage extends React.Component {
constructor(props) {
super(props);
this.state = { test: null };
}
componentDidMount(): void {
$.getJSON("/api/LocaleStrings")
.done(results => {
let JsonString = JSON.parse(results);
this.setState({
test: new LocalizedStrings(JsonString);
});
})
.fail(console.log.bind(console));
}
}
You need to have an "event" that notifies anyone who is interested that test is available:
interface CustomerPageProps {
onLocaleStringsLoaded?: (test:object) => void
}
class CustomerPage extends React.Component<CustomerPageProps> {
static defaultProps {
onLocaleStringsLoaded: () => {} //nothing by default
}
componentDidMount(): void {
$.getJSON("/api/LocaleStrings")
.done(results => {
let JsonString = JSON.parse(results);
const test = new LocalizedStrings(JsonString);
this.props.onLocaleStringsLoaded(test);
}).fail(console.log.bind(console));
}
}
Then at some point in your code you could have:
<CustomerPage onLocaleStringsLoaded={window.console.log.bind(window.console)} />
which will print to the console once the result is available.
I recommend reading up a bit more on how React components share data. The component that needs the data can have an input defined, in which you can pass the test variable. Or using a redux store (which could potentially be a little too complex for your application). If you really want to continue this route. You can always use the window object to set a global variable: window.test = 'bla';. This is available anywhere in the application with console.log(window.test);.
You would have to update your code to:
window.test = new LocalizedStrings(JsonString);.
Verifying that it is set can be done with an interval:
setInterval(function() {
console.log(window.test);
}, 100);

When mapStateToProps in create create a new axios call

I have created a Reactjs component that receives a mapStateToProps function call. Everything works fine except the ajax call using Axios.
The class on a mapStateToProps update needs to call the server and add its payload to the state of the component and update the textarea.
The error I am getting from the console is,
ReactDOMIDOperations.js:47 Uncaught RangeError: Maximum call stack size exceeded
Below is what I have so far. Can anyone show me how to fix this issue?
import React from "react";
import { connect } from "react-redux";
import ApiCalls from "../../../utils/ApiCalls";
const mapStateToProps = state => {
return { passFilePath: state.passFilePath };
};
/**
* This component is a template to display
* widgets of information
*/
class IdeTextEditorClass extends React.Component {
constructor() {
super();
this.state = {
newData: [],
pathData: []
}
}
/**
* Received request from server add it to
* react component so that it can be rendered
*/
componentDidUpdate() {
try {
this.setState({ pathData: this.props.passFilePath[this.props.passFilePath.length - 1] });
} catch (err) {
this.setState({ pathData: '' });
}
console.log('path', this.state.pathData.data);
ApiCalls.readSassFile(this.state.pathData.data)
.then(function (serverData) {
this.setState({ newData: serverData[0].data })
}.bind(this));
}
render() {
try {
this.state.newData
} catch (err) {
this.setState({ newData: '' });
}
return (
<fieldset>
<input type="text" value={this.state.pathData.data} />
<textarea id="ide-text-area" name="ide-text-area" value={this.state.newData} /></fieldset>
)
}
}
const IdeTextEditor = connect(mapStateToProps)(IdeTextEditorClass);
export default IdeTextEditor;
class IdeTextEditorClass extends React.Component {
constructor() {
super();
/*
based on your original code it seems the default data should be empty string ,as you set them to be empty string when you cannot get data from server.
*/
this.state = {
newData: '',
pathData: ''
}
}
/**
* Received request from server add it to
* react component so that it can be rendered
*/
componentDidMount() {
try {
this.setState({ pathData: this.props.passFilePath[this.props.passFilePath.length - 1] });
} catch (err) {
this.setState({ pathData: '' });
}
console.log('path', this.state.pathData.data);
ApiCalls.readSassFile(this.state.pathData.data)
.then(function (serverData) {
this.setState({ newData: serverData[0].data })
}.bind(this));
}
render() {
//by default your newData is already empty string. so skip the checking here.
let path = this.state.pathData ? this.state.pathData.data : '';
return (
<fieldset>
<input type="text" value={path} />
<textarea id="ide-text-area" name="ide-text-area" value={this.state.newData} /></fieldset>
)
}
}
Explanation:
The major change is to change componentDidUpdate to componentDidMount.
Putting the data initializing logic in componentDidMount because:
called only once, thus avoiding the endless update loop mentioned in the comments. Also, initialization logic is usually expected here.
this method is called after initial render, so you can at least display something to user during the wait for data (from server). for example, in your render method, you can check newData and if it is not available, display a loading icon. Then React calls componentDidMount, and fetch your data -> update state -> trigger render again -> displays your input / text area using new data fetched from server. Of course, if you don't want to bother showing a loading icon, it is also fine, because your view will probably be updated quickly, when the ajax call returns.

REACTJS - Getting a TypeError for state variable

I'm try to get array data from a web service api (via JSON) and iterate through the response - but when trying to console.log() the state data from render(), I get this error:
TypeError:this.state.jsonStr is undefined.
class TextFields extends React.Component {
constructor() {
super();
this.state = {
}
};
componentDidMount() {
const apiURL = myConstClass.apiURL;
var apiURL_ = apiURL + '/searchIdea';
fetch(apiURL_, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
// SEND
idea_uniqueid: '',
idea_name: '',
idea_description: '', // FIX: Get from Querystring
})
}).then((Response) => Response.json()).then((findresponse) => {
// RESPONSE
this.setState({ jsonStr: findresponse, });
console.log("SEARCHIDEARESULTS"); // Stopped here - Loop through array and populate below
console.log(this.state.jsonStr);
})
}
// RENDER
// ---------------------------------------------------------------
render() {
const { classes } = this.props;
const dataStr = this.state.jsonStr;
console.log("RENDER"); // Stopped here - Loop through array and populate below
console.log(this.state.jsonStr);
console.log(this.state.jsonStr[0]);
return (
<div></div>
)
}
}
This happens because when the component is rendered for the first time the promise returned by fetch has not yet been resolved so the jsonStr is not found in the state. to fix this add jsonStr to the initial state and assign a value to it
TextFields extends React.Component {
constructor() {
super();
this.state = {
jsonStr: ''
}
}
}
or else you could also add conditions to check whether the jsonStr is set
TextFields extends React.Component {
constructor() {
super();
this.state = {
jsonStr: undefined
}
}
render() {
const { classes } = this.props;
const dataStr = this.state.jsonStr;
console.log("RENDER");
if (this.state.jsonStr) {
console.log(this.state.jsonStr);
console.log(this.state.jsonStr[0]);
}
return (
<div></div>
)
}
}
}
Initially this.state.jsonStr will be undefined. So when executing render() method this.state.jsonStr[0] will throw error.
Try changing console.log(this.state.jsonStr[0]); to console.log(this.state.jsonStr && this.state.jsonStr[0]);
Most of the answers touch on default state values or the error in render. But I see another error in your data fetching function.
From official documentation:
Calls to setState are asynchronous - don’t rely on this.state to reflect the new value immediately after calling setState.
In your componentDidMount method, you are making that mistake:
this.setState({ jsonStr: findresponse, });
console.log("SEARCHIDEARESULTS"); // Stopped here - Loop through array and populate below
console.log(this.state.jsonStr);
If you want to debug, I highly recommend the devTools extension, or put your console.log statements either as a callback to setState or in componentDidUpdate lifecycle.
Few notes on the advice for your render method, Do think of what your component states can be. For e.x. if fetching data from an external service, you may have the following states:
Loading
Error
No Data Found
Data Found
When you do: const dataStr = this.state.jsonStr ? this.state.jsonStr : "", you may lose the possibility to distinguish between these states.
I would advice to make that state state explicit (there are many techniques out there, I am leaving them out for brevity), but at the least I would suggest intelligent defaults. For e.x.:
constructor() {
super();
this.state = {
jsonStr : undefined
}
};
Your api call will either return value, null or an empty Array (if it is an array). May also throw an error, which you can catch with componentDidCatch.
You can now handle these cases in your render method easily.
Looks like you're trying to access to this.state.jsonStr before you define it and as a result you get the error:
TypeError:this.state.jsonStr is undefined.
Try setting it in the constructor:
constructor() {
super();
this.state = {
jsonStr: ''
}
};
Please try this modified code:
render() {
const { classes } = this.props;
const dataStr = this.state.jsonStr ? this.state.jsonStr : "" ;// I made a change here
console.log("RENDER"); // Stopped here - Loop through array and populate below
console.log(dataStr);
return (
<div></div>
)
}
}

Meteor - how to give tracker autorun a callback

I have a little piece of code that renders data from the database according to the path name. My only problem is that when I try to retrieve that data, using this.state.note._id it returns an error that says it cannot find _id of undefined. How would I access my object that is put into a state? It only gives the error when I try to access the items inside the object such as _id
import React from "react";
import { Tracker } from "meteor/tracker";
import { Notes } from "../methods/methods";
export default class fullSize extends React.Component{
constructor(props){
super(props);
this.state = {
note: [],
document: (<div></div>)
};
}
componentWillMount() {
this.tracker = Tracker.autorun(() => {
Meteor.subscribe('notes');
let note = Notes.find({_id: this.props.match.params.noteId}).fetch()
this.setState({ note: note[0] });
});
}
renderDocument(){
console.log(this.state.note);
return <p>Hi</p>
}
componentWillUnmount() {
this.tracker.stop();
}
render(){
return <div>{this.renderDocument()}</div>
}
}
I know that the reason it is returning undefined is because (correct me if I am wrong) the page is rendering the function before the the tracker could refresh the data. How would I get like some sort of callback when the tracker receives some data it will call the renderDocument function?
You're initializing your note state as an array but then you're setting it to a scalar later. You're also not checking to see if the subscription is ready which means that you end up trying to get the state when it is still empty. The tracker will run anytime a reactive data source inside it changes. This means you don't need a callback, you just add any code you want to run inside the tracker itself.
You also don't need a state variable for the document contents itself, your render function can just return a <div /> until the subscription becomes ready.
Note also that .findOne() is equivalent to .find().fetch()[0] - it returns a single document.
When you're searching on _id you can shorthand your query to .findOne(id) instead of .findOne({_id: id})
import React from "react";
import { Tracker } from "meteor/tracker";
import { Notes } from "../methods/methods";
export default class fullSize extends React.Component{
constructor(props){
super(props);
this.state = {
note: null
};
}
componentWillMount() {
const sub = Meteor.subscribe('notes');
this.tracker = Tracker.autorun(() => {
if (sub.ready) this.setState({ note: Notes.findOne(this.props.match.params.noteId) });
});
}
renderDocument(){
return this.state.note ? <p>Hi</p> : <div />;
}
componentWillUnmount() {
this.tracker.stop();
}
render(){
return <div>{this.renderDocument()}</div>
}
}

What is the proper way to getInitialState with remote data in Reactjs?

IT IS SOLVED. The code was ok, the problem was with improper import.
It is a long post (due to code samples). I'll appreciate your patience and will be very thankful for your help!
We have a RoR back-end and React on the front-end and we are using alt as an implementation of flux. We also use babel to compile ES6 to ES5. So the problem is that I can not render component due to 2 errors.
First is Uncaught TypeError: Cannot read property 'map' of undefined
It appears on the render function of MapPalette component:
render() {
return (
<div>
{this.state.featureCategories.map(fc => <PaletteItemsList featureCategory={fc} />)}
</div>
);
}
And the second is Uncaught Error: Invariant Violation: receiveComponent(...): Can only update a mounted component.
So here is the whole MapPalette component
"use strict";
import React from 'react';
import PaletteItemsList from './PaletteItemsList';
import FeatureCategoryStore from '../stores/FeatureTypeStore';
function getAppState() {
return {
featureCategories: FeatureCategoryStore.getState().featureCategories
};
}
var MapPalette = React.createClass({
displayName: 'MapPalette',
propTypes: {
featureSetId: React.PropTypes.number.isRequired
},
getInitialState() {
return getAppState();
},
componentDidMount() {
FeatureCategoryStore.listen(this._onChange);
},
componentWillUnmount() {
FeatureCategoryStore.unlisten(this._onChange);
},
render() {
return (
<div>
{this.state.featureCategories.map(fc => <PaletteItemsList featureCategory={fc} />)}
</div>
);
},
_onChange() {
this.setState(getAppState());
}
});
module.exports = MapPalette;
FeatureCategoryStore
var featureCategoryStore = alt.createStore(class FeatureCategoryStore {
constructor() {
this.bindActions(FeatureCategoryActions)
this.featureCategories = [];
}
onReceiveAll(featureCategories) {
this.featureCategories = featureCategories;
}
})
module.exports = featureCategoryStore
FeatureCategoryActions
class FeatureCategoryActions {
receiveAll(featureCategories) {
this.dispatch(featureCategories)
}
getFeatureSetCategories(featureSetId) {
var url = '/feature_categories/nested_feature_types.json';
var actions = this.actions;
this.dispatch();
request.get(url)
.query({ feature_set_id: featureSetId })
.end( function(response) {
actions.receiveAll(response.body);
});
}
}
module.exports = alt.createActions(FeatureCategoryActions);
And the last - how I render React component.
var render = function() {
FeatureCategoryActions.getFeatureSetCategories(#{ #feature_set.id });
React.render(
React.createElement(FeatureSetEditMap, {featureSetId: #{#feature_set.id}}),
document.getElementById('react-app')
)
}
First of all, the first error you get:
Uncaught TypeError: Cannot read property 'map' of undefined
Is because this.state in your component is undefined, which means that you probably haven't implemented getInitialState in your component.
You haven't included the full implementation of your component, which we need to see to be able to help you. But let's walk through their example of a view component:
var LocationComponent = React.createClass({
getInitialState() {
return locationStore.getState()
},
componentDidMount() {
locationStore.listen(this.onChange)
},
componentWillUnmount() {
locationStore.unlisten(this.onChange)
},
onChange() {
this.setState(this.getInitialState())
},
render() {
return (
<div>
<p>
City {this.state.city}
</p>
<p>
Country {this.state.country}
</p>
</div>
)
}
})
They implement getInitialState here to return the current state of the store, which you'll then be able to use in the render method. In componentDidMount, they listen to change events from that store so that any events occuring in that store coming from anywhere in your application will trigger a re-render. componentWillUnmount cleans up the event listener. Very important not to forget this, or your app will leak memory! Next the onChange method (which could be have whatever name, it's not an internal React method), which the store will call when a change event occurs. This just sets the state of the component to whatever the stores state is. Might be a bit confusing that they call getInitialState again here, because you're not getting the initial state, you're getting the current state of the store.
Another important note here is that this example won't work off the bat with ES6/ES2015 classes, because React no longer autobinds methods to the instance of the component. So the example implemented as a class would look something like this:
class LocationComponent extends React.Component {
constructor(props) {
super(props)
this.state = this.getState();
this.onChangeListener = () => this.setState(this.getState());
}
getState() {
return locationStore.getState();
}
componentDidMount() {
locationStore.listen(this.onChangeListener);
}
componentWillUnmount() {
locationStore.unlisten(this.onChangeListener)
}
render() {
return (
<div>
<p>
City {this.state.city}
</p>
<p>
Country {this.state.country}
</p>
</div>
);
}
}
Sorry for your wasted time on reading it, but I figured it out and the reason of trouble was my silly mistake in importing. Essentially, I've imported another Store with the name of needed.
So, instead of import FeatureCategoryStore from '../stores/FeatureTypeStore';
It should be import FeatureCategoryStore from '../stores/FeatureCategoryStore';

Resources