React - Promise - "this" is undefined - javascript

I'm sure this is a beginner question, but actually I can't figure out, why this is undefined.
But let's start from the beginning. I'm using react with react-redux to load data. In this special case I first want to load all positions of a project and then load the verfication files. That are 2 queries, so I want to be sure, that the positions are loaded completely before start loading the verifications. So I thought - oohh, let's use a promise here.
It starts with the method loadVerifications(event). In this method, the method loadProjectPositions(event) is being called. I can see that the project positions are loaded correctly.
When the positions are loaded it should load then the verification files (.then(()...).
Actually in this .then() method, this is undefined, but why?
class Kundenkommunikation extends Component {
constructor(p) {
super(p);
this.state = {
};
this.loadProjectPositions = this.loadProjectPositions.bind(this);
this.loadVerifications = this.loadVerifications.bind(this);
}
loadProjectPositions(event) {
return new Promise((resolve,reject) => {
this.props.getProjektPositionenByProjektIdForModulId(13, event.value);
resolve(this)
})
}
loadVerifications(event) {
this.loadProjectPositions(event)
.then(() => {
this.props.projektpositionen && Object.values(this.props.projektpositionen).map((position) => {
if(position.nachweis != null) {
this.props.createBericht(position.nachweis.id,'SherpaVerbinder');
}
});
})
}
UPDATE:
Even when I'm bind loadVerifications in the constructor, it doesn't work. this stays undefined.

You should use bind(this) method for loadVerifications to get a component's context from the external function or
Another is arrow functions loadVerifications= (event) => {...}

Ok, thanks for all hints and tipps. Lately it turned out, that Chrome did not show the data for whatever reason, but the data was available. I could find out by simply add an console.log(this.props) into the code, then it showed me all props and I was able to see that this is not undefined.
But the real issue was then, that the data was not loaded at this time. So I needed to add a then into the method loadProjectPositions to be sure, that the data is returned before resolving the Promise.
loadProjectPositions(event) {
return new Promise((resolve,reject) => {
this.props.getProjektPositionenByProjektIdForModulId(13, event.value)
.then(() => {
resolve(this)
});
})
}
After adding this, all data was loaded correctly at that time and then all worked.

Related

ReactJS: multiple setStates happening asynchronously, state not updated

Facing an issue where state is not updated, before function (filterAndSort) located in render is fired. I have added a console.log statement within the function, and only one of the states gets updated. My code seems logical to me, as I have set an if condition where function is fired only after setState occurs
Full error message:
Warning: Can't call setState (or forceUpdate) on an unmounted
component. This is a no-op, but it indicates a memory leak in your
application. To fix, cancel all subscriptions and asynchronous tasks
in the componentWillUnmount method.
However, i'm suspecting that because i'm having multiple setStates happening asynchronously.
I'm thinking that perhaps, i need to re-write the componendDidMount to setState for all variables only after all axios requests are fetched.
Multiple Axios Requests Into ReactJS State
Another solution I think would be to having the returned results of the function stored as a state instead of a variable, then add a componentDidUpdate.
componentDidUpdate(prevProps, prevState) {
if (this.state.value > prevState.value) {
this.filterAndSort();
}
}
Component
class Results extends Component {
constructor(){
super()
this.state = {
results: [],
races: [],
arr = []
isLoading: true
};
}
componentDidMount(){
const oneRequest = axios.get(URL_ONE)
.then(response =>
response.data.data.map(result => ({...
))
)
.then(results => this.setState({results, isLoading: false}))
const twoRequest = axios.get(URL_TWO)
.then(response =>
response.data.data.map(race => ({...}))
)
.then(races => this.setDefault(races))
}
setDefault = (races) => {
........
this.setState({arr, races, isLoading:false})
}
filterAndSort = (races, results) => {
console.log(races, results)
.......
}
render() {
const{races, results} = this.state
if (isLoading == true) {
return (
<div>
<p>Loading...</p>
</div>
)
} else {
return (
<div>
<BarChart
qualData={this.filterAndSort(races, results)}
raceData={this.filterAndSort(races, results)}
width="1200"
height="500" />
</div>
);
}
}
}
export default Results;
Well, there are a number of things that come to my mind.
First, you set the isLoading to false only when you have one of two pieces of data, whichever comes first, thus the render method will at some point call your function with either empty races or results.
Another thing, you are asynchronously calling setState. By the time the request is finished your component may not exist any more thus it will try to update a non existing component and fail with that error.
For the first issue, one possible solution is to have two isLoading-variables for both results and races.
For the second issue (setState called on an unmountedComponent), its a bit more complicated, because you need to somehow cancel the request. I recommend reading more on this, the general advice is to move your data out of the components using a library like redux. If you google cancel promise on unmount you will find discussion on this. You can also deal with it using an "isMounted" variable, which will work as an ugly patch.
So, as soon as request one (or two) is completed, setState is called, the component is then re-rendered. The isLoading is now true so filterAndSort is called with results (or races) but not both because the second request is still pending.
Finally, in your render method your isLoading needs to be first defined (i assume its ok in your code but not in the question) and the is True comparison can be better put as
if (isLoading) { instead of if (isLoading == True) {
You're right. When making multiple requests its always best practice to wait for them all to resolve before moving forward. For that you can use the Promise library which is built into ES6. Also, for fetching of data its best practice, so far as i've seen, to do it in componentWillMount(). I'll also add that the context of this changes when inside an async function. So in your componentWillMount():
let promiseArr = [axios.get(URL_ONE), axios.get(URL_TWO)];
let _this = this;
Promise.all(promiseArr)
.then((resp) => {
// Call _this.setState() here. Response from URL_ONE
// will be available in resp.data[0] and URL_TWO in resp.data[1]
}).catch((err) => {
console.log(err);
})
Also, in your constructor:
constructor(){
super()
this.state = {
results: [],
races: [],
arr = []
isLoading: true
};
this.setDefault = this.setDefault.bind(this);
this.filterAndSort = this.filterAndSort.bind(this);
}
Using .bind to make sure that the context of this is referrering to the current instance of the class when calling those methods.
I think after doing those things that error will go away. Hope that helps.

ReactJS - REST API query inside .map function?

How do I generate repetitive mark-up elements in a React component, that also require a REST API query for each of them? Initially I had the calls completing in a for-each loop within the componentDidMount function. But that required calling setState inside the loop, in order to trigger a re-Render. The behavior was unstable, and apparently that technique is not a good idea.
So, now I'm trying to get the queries to run inside the .map function of my render (see below). But that's producing this error.
Is there a way to return the element value instead of a promise? (Probably more of a JS question than a React one...)
Or am I going about this the wrong way altogether?
return (<div>
{this.props.currentProject.Tasks.map((task => {
return ProjectsService.getTaskBaselines(this.props.currentProject.PWAGuid, task.TaskId, this.props.context).then((baseline: any): any => {
return (<div>{task.TaskName}</div>);
})
}))}
</div>);
It seems like you want to query all APIs and then handle the response once all are settled.
Try this:
const promises = this.props.currentProject.Tasks.map((task => {
return new Promise(function(resolve, reject) {
resolve(task);
});
Promise.all(promises).then((data) => {
this.setState({
// act on all of your data
});
});
Then render this.state in your render() function.

Daisy-chaining promises in Vue Actions results in infinite loop

i have a question regarding Javascript Promises in a VueJS setting, i have an application that uses an Action to either fetch a list of Countries from IndexedDB (if it's set ) or from an API by making an Axios HTTP Request.
Now, i'm returning a promise from the action because i want to be able to trigger some popups in the UI when this task is completed, and on top of that both Axios and Dexie(which im using for IndexedDB) run asyncronously through Promises themselves.
getCountries({commit, dispatch}) {
commit(types.MUTATIONS.SET_LOADING, true, {root: true})
commit(types.MUTATIONS.SET_LOADER_MESSAGE, "Loading Countries Data...", {root: true})
return new Promise((resolve, reject) => {
db.countries.count().then(value => {
if(value > 0) {
console.log("Loading Countries from IndexedDB")
db.countries.toArray().then(collection => {
commit(types.MUTATIONS.COUNTRIES.SET, collection, {root: true})
resolve(collection);
})
} else {
fetchCountriesData().then(data => {
console.log("Loading Countries from API Call")
commit(types.MUTATIONS.COUNTRIES.SET, data, {root: true})
db.countries.bulkAdd(data)
resolve(data)
}).catch(error => {
reject(error)
})
}
})
})
}
That is the code for the action, it simply does what i described above, the problem is that this results in an infinite loop where the LOADER Mutations get triggered over and over again.
Why exactly is this going on? Can anyone help me make sense of this? It seems that it runs the initial API action, but THEN after that, with the countries already loaded, it loops and runs again this time invoking the indexeddb mutation as well, which is strange, if i resolve it shouldn't it just end there?
EXTRA::
The action is invoked in a view that i have in my application, i invoke this in the created() hook so that i make sure that the list of countries is always loaded in my Vuex State.
created() {
this.getAllCountries().then(response => {}).catch(error => {
snackbar("Unable to load countries!", "error")
}).then(() => {
this.setLoadingStatus(false);
})
}
In this context, it does nothing if it's ok, but that might change in the future, on errors it should show a popup informing the users that the data could not be loaded, and in either case it should hide the loading bar (which is also handled through Vuex)
Could this be the cause of the problem?
Nevermind, i had a logic error in my code, basically in order to prevent anyone being able to click items while loading i set the view conditionally with a v-if="loading" so that if loading only show the Loader div and otherwise show the actual layout.
The problem with this approach is that it will re-trigger the created hook each time the main view is shown again, thus causing my silly loop.
Hope this helps someone in the future.

React 2-stage render from Promise not rendering after first promise resolved

My app sometimes receives a large volume of data it needs to parse and render. I want to parse the first couple of messages and render them to give the user something to work on while the rest of the messages parse in the background. The problem is, the first render in the image below does not result in a screen draw Chrome, Firefox, or Safari.
parse(data, show) {
if (data === null) {
...
}
else {
if (data.length > 100) {
let taste = [];
taste.push(data.pop());
taste.push(data.pop());
console.log('start');
const messagePromise = this.props.parser.parseMessages(taste);
console.log('promise 1 created');
const restPromise = messagePromise.then(result => {
console.log('first chunk resolved, setting state!');
if (this.state.messageDetail === null)
this.setState({messageDetail: result[0]});
this.setState({parsedMessages: this.state.parsedMessages.concat(result)});
return this.props.parser.parseMessages(data);
});
console.log('promise 2 created');
restPromise.then(result => {
console.log('second chunk resolved, setting state!');
if (this.state.messageDetail === null)
this.setState({messageDetail: result[0]});
this.setState({parsedMessages: this.state.parsedMessages.concat(result)});
});
console.log('leaving parse');
}
This method parse is passed as a prop to a child component who calls it when he has some messages to parse. There are 2 other child components that display the list of messages and the details of a selected message. These components have parsedMessages and messageDetail passed as props so when the state changes here in the parent the children rerender themselves.
Anyone seen an issue where a render doesn't result in a screen draw like this? I thought I was blocking somewhere but seeing the render in the log at 19:16:34.630 makes me think something else is going on.
I'm using latest React (15.6.1) and up-to-date browsers.
So looks like the page doesn't draw until the onClick returns. I thought that would have happened right away, after promise 2 was created but before the resolve happened. Maybe I'm misunderstanding something about how promises work.
<RaisedButton onClick={this.handleSubmit.bind(this)} label="Parse" style={leftButtonStyle} secondary={true} disabled={!this.state.parseEnabled}/>
handleSubmit() {
let messages = this.state.value.split('\n');
this.props.parser(messages, true);
}
handleSubmit() should be done when this.props.parser(...) returns, the onClick() is bound to handleSubmit() and this.props.parser() is bound to the parse() method above which we can see from the console log exits at 19:16:34:619
bloodyowl in https://github.com/facebook/react/issues/7678 solved the issue. Still not sure why a promise is not sufficient (maybe because it's being transpiled?) but doing the initial partial render() and then in componentDidUpdate() -- doesn't work if you do it in the setState() callback -- wrapping the stage 2 processing in a setTimeout(doParsing, 0) breaks the chain so to speak and allows the initial render to paint.

Testing fetch() method inside React component

I have an App component that is responsible for rendering child input components, it is also responsible for handling fetch requests to the Twitch API via a method called channelSearch. I have tried to adhere to suggested best practices outlined here for working with ajax/fetch with React.
The method is passed down through props and called via a callback.
Note the fetch method is actually isomorphic-fetch.
channelSearch (searchReq, baseUrl="https://api.twitch.tv/kraken/channels/") {
fetch(baseUrl + searchReq)
.then(response => {
return response.json();
})
.then(json => {
this.setState({newChannel:json});
})
.then( () => {
if (!("error" in this.state.newChannel) && this.channelChecker(this.state.newChannel._id, this.state.channelList) ) {
this.setState(
{channelList: this.state.channelList.concat([this.state.newChannel])}
);
}
})
.catch(error => {
return error;
});
}
I am currently trying to write a test for the channelSearch method. I am currently using enzyme and jsdom to mount the entire <App> component in a DOM. Find the child node with the callback, simulate a click (which should fire the callback) and check to see if the state of the component has been changed. However, this does not seem to work.
I have also tried calling the method directly, however, I run into problems with this.state being undefined.
test('channel search method should change newChannel state', t => {
const wrapper = mount(React.createElement(App));
wrapper.find('input').get(0).value = "test";
console.log(wrapper.find('input').get(0).value);
wrapper.find('input').simulate("change");
wrapper.find('button').simulate("click");
console.log(wrapper.state(["newChannel"]));
});
I am really lost, I am not sure if the method itself is poorly written or I am not using the correct tools for the job. Any guidance will be greatly appreciated.
Update #1:
I included nock as recommended in comments, test now looks like this:
test('channel search method should change newChannel state', t => {
// Test object setup
var twitch = nock('https://api.twitch.tv')
.log(console.log)
.get('/kraken/channels/test')
.reply(200, {
_id: '001',
name: 'test',
game: 'testGame'
});
function checker() {
if(twitch.isDone()) {
console.log("Done!");
console.log(wrapper.state(["newChannel"]));
}
else {
checker();
}
}
const wrapper = mount(React.createElement(App));
wrapper.find('input').get(0).value = "test";
wrapper.find('input').simulate("change");
wrapper.find('button').simulate("click");
checker();
});
This still does not seem to change the state of the component.
fetch is asynchronous but you're testing synchronously, you need to either mock fetch with a synchronous mock or make the test asynchronous.
nock may work for you here.
I suggest you create a sample of your test using plnkr.
I agree with Tom that you're testing synchronously. It would of course be helpful to show off your actual component code (all of the relevant portions, like what calls channelSearch, or at the least describe it by saying e.g. "channelSearch is called by componentDidMount()". You said:
I run into problems with this.state being undefined.
This is because this.setState() is asynchronous. This is for performance reasons, so that React can batch changes.
I suspect you'll need to change your code that is currently:
.then(json => {
this.setState({newChannel:json});
})
to:
.then(json => {
return new Promise(function(resolve, reject) {
this.setState({newChannel:json}, resolve);
})
})
Note that your checker() method won't work. It's looping, but twitch.isDone() will never be true because it never has a chance to run. Javascript is single threaded, so your checker code will run continuously, not allowing anything else in between.
If you set up the plnkr, I'll take a look.
Refactor out the fetch code from the component then pass it it to the component as a callback function in the properties.
export class Channel extends React.Component {
componentDidMount() {
this.props.searchFunction().then(data => this.setState(data));
}
render() {
return <div>{this.state}</div>;
}
}
Uage:
function channelSearch(name) {
return fetch(`https://api.twitch.tv/kraken/search/channels?query=${name}`);
}
<Channel searchFunction={channelSearch} />
Now you can test the API functionality independently of the component.

Categories

Resources