I have a problem with a simple function which where I want to call an API and then do something with the response. Basically, I just want to set my react component state to the response I get and then navigate to the other page The problem is that my code executes another part of the function before an API call is finished, and I end up on another page with console.log of undefined
There is my function:
const startNewGame = () => {
GameService.startGame()
.then((response) => {
setGame(response.data);
console.log(game);
navigate('intro');
})
.catch((e) => {
console.log(e);
});
};
I can wrap my navigate into if(!game !== undefined) but then I have to click two or more times on a button.
Thank you all guys for help :)
You probably do several things incorrect at the same time, so to understand what exactly is wrong might take some time. Take these steps to debug your code:
Make each .then call do only one thing (and stick to that principle in other cases). You could chain as many .then as you like. Moreover you could return data from one .then to the next, so your code might look like this:
GameService.startGame()
.then(response => response.data)
.then(data => {
setGame(data)
})
.then(() => {
console.log(game);
navigate('intro');
})
.catch((e) => {
console.log(e);
});
Understand your components composition. Where exactly are you saving your response? is it just local component useState or some Context Api state that wraps the app? When you navigate to "other page" your "current page" state will be unavailable to "other page" unless you keep the data somewhere up in the tree when both pages could access that.
For further references keep in mind that setGame is asynchronous, so you need to wait for the state to be updated to make sure that its updated.
Try this:
const startNewGame = () => {
GameService.startGame()
.then((response) => {
setGame(response.data);
// game is not updated yet
// console.log(game); <- Remove this line
// navigate('intro'); <- Remove this line
})
.catch((e) => {
console.log(e);
});
};
useEffect(()=>{
if(game !== undefined){
navigate('intro')
}
}, [game])
Related
I'm creating a small project with the mern stack. In the homepage of this project, you can see the things that the current user has. THe problem is that the list can change in every moment, because other user can happend to that list other stuff. SO, to make it refresh, I put it in the useEffect hook. The problem now is that my server is reciving a tons of request, and always the same one. I don't know if is the case to set a timer that rerender after x second, to make the work of my server lighter, or there is a way to remake the request to the server in some case. Here is the small code that manage the request:
const [file, setFile] = useState([]);
useEffect(() => {
axios.post("http://127.0.0.1:5050/file/getfilesbycreator",{creator:localStorage.getItem('user')})
.then(res => {
setFile(res.data);
})
.catch(err => console.log(err))
})
If someone has any suggestion, please tell me. I read something about rerender react, but I didn't found out better way then a timer, or something similar.Thanks
As the second parameter of your useEffect, you have to indicate which variable you are looking for to rerender.
If you want the useEffect to render only one time, you have to put [] as second parameter like that :
useEffect(() => {
axios.post("http://127.0.0.1:5050/file/getfilesbycreator",{creator:localStorage.getItem('user')})
.then(res => {
setFile(res.data);
})
.catch(err => console.log(err))
}, [])
If you have a variable that can change, for example 'file', just add it in the array :
useEffect(() => {
axios.post("http://127.0.0.1:5050/file/getfilesbycreator",{creator:localStorage.getItem('user')})
.then(res => {
setFile(res.data);
})
.catch(err => console.log(err))
}, [file])
I have the following code:
componentDidMount () {
fetch('/data')
.then(res => res.json())
.then(data => this.setState({data}));
this.countVar();
}
countVar () {
//iterate through this.state.data and filter out certain values
}
The countVar() function isn't loading inside componentDidMount(). When I did console.log(this.state.data) right after the componentDidMount()-function, it returned an empty array, so I guess that's the reason.
I tried to use componentDidUpdate() instead if countVar(), but I didn't consider that that would create an infinite loop. I don't quite know how to handle this.
The data that I'm fetching consists of several object arrays. in countVar() I'm filtering out a certain object array, hence the structure of my functions.
Does anyone know how I could solve this problem?
fetch('/data')
.then(res => res.json())
.then(data => this.setState({data}));
This code is async. This means that when the fetch is complete, it will run the functions passed into the then callback. You're running your iteration function directly after registering the promise; not after the promise is resolved.
ALSO
this.setState is not synchronous. You can't guarantee that after you request that the state is set, it is set. It is set some time afterward, but React provides an option for this; you pass in a callback function once state setting is complete.
this.setState({ data }, () => console.log('do something'))
You should call this.countVar inside the then of the Promise otherwise it will run before the fetch Promise is over.
Like this:
componentDidMount () {
fetch('/data')
.then(res => res.json())
.then(data => {
this.setState({data}, this.countVar);
});
}
I'm running this.countVar as a callback to setState so that it runs once setState is done.
I have this function that is in the top level component that I'd like to reuse but on a different piece of state. In the following code sample, I'm using it on userData, but I'd like to be able to reuse it on a piece of state called repoData, either within the same component it's child, or outside. Just not sure how to go about it, since, if I do something like pass it the states as args, setting the state would throw an error because it would look like: this.setState({this.state.userData: data}).
fetchUserData(params) {
fetch('https://api.github.com/users/' + params)
.then(response => {
if (!response.ok) {
throw Error("Network failure")
}
return response;
})
.then(data => data.json())
.then(data => {
this.setState({
userData: data
})
}, () => {
this.setState({
requestFailed: true
})
})
}
Your question is more about refactoring your code I believe. Here you can look at it as moving out the logic of fetching user data asynchronously. Your component need not know who(fetch, axios) is doing the work of fetching the data for it. Also it is not required for a component to know from where(https://api.github.com/users/' + params) to get this data.
One possible way of doing it is to move your fetch call in a separate function which will return the data to your component after fetching it asynchronously. Then it is responsibility of component to handle the data the way it want to.
So as you can see the key point here is identifying and separating out the responsibilities of each piece of code. Advantages of doing this will be clean code structure, less number of lines, maintainable code and most importantly easily testable code.
I hope this solves your problem. If you are interested to learn about such techniques more you can read about DRY principle and refactoring techniques.
Edit 1:
I have created this code sample for your reference
What are JavaScript Promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Some samples for fetch: https://davidwalsh.name/fetch
I personally recommend using axios. You can see comparison of fetch and axios here: https://medium.com/#thejasonfile/fetch-vs-axios-js-for-making-http-requests-2b261cdd3af5
If I'm understanding you correctly
you should move the function to a file x.js
it should accept an other param which is the name of the new (piece) of state
it should also accept a param for the this.setState of the component calling it.
import it and use it in componentDidMount as normal
```
export const fetchUserData(params,setState , stateParam) {
fetch('https://api.github.com/users/' + params)
.then(response => {
if (!response.ok) {
throw Error("Network failure")
}
return response;
})
.then(data => data.json())
.then(data => {
setState({
[stateParam]: data
})
}, () => {
setState({
requestFailed: true
})
})
}
you can call it as follows
import { fetchUserDate } from "./path/to/x"
fetchUserDate("param" , this.setState, "xOnState")
I am currently building a web app which contains a menu. The menu can change depending on a couple of variables, and so I make a call to an api, to request the correct menu items that should be shown.
server.get('/api/getMenu', (req, res) => {
getMenu((err, content) => {
if(!err) {
res.send(content);
} else {
res.status(500).send();
}
})
});
This request is working perfectly fine, and I am then dispatching an action that will call this API on componentWillMount
export function fetchMenuItems() {
return (dispatch) => {
fetch('/api/getMenu')
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
})
.then((response) => response.json())
.then((results) => dispatch(fetchSuccess(results)))
}
}
export function fetchSuccess(results) {
return {
type: 'FETCH_SUCCESS',
menuItems: results
};
}
This again is working fine and loading the menu items, however it looks strange as the menu items seem to render on the page after the rest of the page has already loaded (I'm assuming due to how long the request takes).
Is there any way to preload the menu items before the whole page actually renders? I've heard of promises but know little of them, would this potentially be a solution?
I'm assuming you're looking for componentWillMount react lifecycle method.
componentWillMount will be invoked only once right before your component rendered at the first time. This is a perfect place to put any pre-loading data logic.
Hope this helps.
So I'm trying to fetch all 'places' given some location in React Native via the Google Places API. The problem is that after making the first call to the API, Google only returns 20 entries, and then returns a next_page_token, to be appended to the same API call url. So, I make another request to get the next 20 locations right after, but there is a small delay (1-3 seconds) until the token actually becomes valid, so my request errors.
I tried doing:
this.setTimeout(() => {this.setState({timePassed: true})}, 3000);
But it's completely ignored by the app...any suggestions?
Update
I do this in my componentWillMount function (after defining the variables of course), and call the setTimeout right after this line.
axios.get(baseUrl)
.then((response) => {
this.setState({places: response.data.results, nextPageToken: response.data.next_page_token });
});
What I understood is that you are trying to make a fetch based on the result of another fetch. So, your solution is to use a TimeOut to guess when the request will finish and then do another request, right ?
If yes, maybe this isn't the best solution to your problem. But the following code is how I do to use timeouts:
// Without "this"
setTimeout(someMethod,
2000
)
The approach I would take is to wait until the fetch finishes, then I would use the callback to the same fetch again with different parameters, in your case, the nextPageToken. I do this using the ES7 async & await syntax.
// Remember to add some stop condition on this recursive method.
async fetchData(nextPageToken){
try {
var result = await fetch(URL)
// Do whatever you want with this result, including getting the next token or updating the UI (via setting the State)
fetchData(result.nextPageToken)
} catch(e){
// Show an error message
}
}
If I misunderstood something or you have any questions, feel free to ask!
I hope it helps.
try this it worked for me:
async componentDidMount() {
const data = await this.performTimeConsumingTask();
if (data !== null) {
// alert('Moved to next Screen here');
this.props.navigator.push({
screen:"Project1.AuthScreen"})
}
}
performTimeConsumingTask = async() => {
return new Promise((resolve) =>
setTimeout(
() => { resolve('result') },
3000
)
);
}