fetching data in React useEffect() after adding item to database using graphql and axios crashes the app - reactjs

Initially the initialFetch is true, so whenever the component renders graphql and axios fetch data from db. Then initialFetch is set to false.
Once an event is added to db via graphql and axios there added state variable is set to true. Since useEffect depends on added it should re-render the component and should fetch the data from db. But for some reason it fails as I mentioned below axios fails at communicating with the server.
Note! I Used GraphQL for fetching data from MongoDB
const [added, setAdded] = useState(false)
const [initialFetch, setInitialFetch] = useState(true)
useEffect(() => {
const fetchEvents = () => {
console.log('inside fetchEvents()')
const headers = {
'Content-Type': 'application/json'
}
const requestBody = {
query: `
query {
events {
_id
title
description
price
}
}
`
}
const body = JSON.stringify(requestBody)
console.log('awaiting for events from db')
axios.post('http://localhost:5000/graphql', body, {headers}).then((res) => {
console.log('events fetched from db')
setEvents(res.data.data.events)
}).catch((err) => {
console.log(err)
})
}
if (initialFetch) {
setInitialFetch(false)
console.log('initial fetch')
fetchEvents()
}
if (added) {
setAdded(false)
console.log('added, fetching again')
fetchEvents()
}
}, [added, initialFetch])
Here axios fails to add data to db and catch(err) block is executed after waiting over 2 minutes and the app crashes. The below code where axios posts data continuously keeps failing every time I try.
const handleConfirm = () => {
// request to backend
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authContext.token}`
}
const requestBody = {
query: `
mutation {
createEvent(title: "${title}", description: "${desc}", price: ${price}, date: "${date}") {
_id
title
description
price
}
}
`
}
const body = JSON.stringify(requestBody)
console.log('adding to db')
axios.post('http://localhost:5000/graphql', body, {headers}).then((res) => {
setAdded(true)
console.log('added item to db')
console.log(res.data)
}).catch((err) => {
console.log(err)
})
}
the initial fetch happens and I add data to db. After adding to db I should be re-fetched the events from db, instead that re-render fails and the app crashes.

That error is a classic sign that you have an infinite loop in your code. It is particularly common when that infinite loop is due to infinite recursion. Each time you call a function, JS has to allocate some stack space for the stackframe. If that function always calls itself, then it will keep trying to allocate more and more space until eventually, it crashes due to having no more memory available to allocate from.
Try removing the unguarded call to fetchEvents() in your useEffect() code block.

Yes, the app will definitely crash as it is updating the state recursively and indefinitely.
as every time the value of added is updated the useEffect is fired and as the useEffect fires it triggers axios.post which inturn again updates the state added

Related

React repeatedly requests API data

I'm requesting API data through axios using RapidAPI's Apis.
I followed all the documentations provided in RapidAPI with a relatively simple code. However, when I log the values, it keeps repeatedly requesting data over and over and this in turn hikes up my requests from RapidAPI and uses a lot of resources and money. I can't figure out how to only retrieve the values ONCE. Here are my codes.
My React .tsx file
const [sportsData, setSportsData] = useState()
const fetchSportsData = () => {
axios
.request(testApi)
.then((response) => {
setSportsData(response.data)
})
.catch((error) => {
console.log(error)
})
}
fetchSportsData()
console.log(sportsData)
My Api File
export const testApi = {
method: 'GET',
url: 'https://api-football-v1.p.rapidapi.com/v3/timezone',
headers: {
'X-RapidAPI-Key': '-------removed on purpose ---------',
'X-RapidAPI-Host': 'api-football-v1.p.rapidapi.com'
}
}
I am just setting the data using useState but it seems to repeatedly rerender whenever a value is stored. I've tried many roundabout ways but it seems to repeatedly request over and over again. Has anyone used API's from RapidAPI before?
While I don't know why useState will still repeatedly retrieve API data with axios, this is a workaround as commented by Sergey Sosunov.
On the React File
const [sportsData, setSportsData] = useState()
const fetchSportsData = () => {
axios.request(testApi).then((response) => {
setSportsData(response.data)
})
.catch((error) => {
console.log(error)
})
}
useEffect(()=> {
fetchSportsData()
},[])
On dev mode, the useEffect will run twice on load and depending on your API provider, this may mean calling the API twice on every load which may double your costs unnecessarily, this only happens in development mode as outlined in react documentation, what you can do is include a useRef variable.
const firstRender = useRef(false)
useEffect(()=>{
if (firstRender.current) {
fetchSportsData()
} else {
firstRender.current = true;
}
},[])
Remember that this code may not execute perfectly when in production as useEffect is only run once and the above code needs it to run twice in order to change the state of firstRender to true before retrieving the API call. This is just to help you lower your development costs.

react-query: useQuery returns undefined and component does not rerender

I'm playing around with reactQuery in a little demo app you can see in this repo. The app calls this mock API.
I'm stuck on a an issue where I'm using the useQuery hook to call this function in a product API file:
export const getAllProducts = async (): Promise<Product[]> => {
const productEndPoint = 'http://localhost:5000/api/product';
const { data } = await axios.get(productEndPoint);
return data as Array<Product>;
};
In my ProductTable component I then call this function using:
const { data } = useQuery('products', getAllProducts);
I'm finding the call to the API does get made, and the data is returned. but the table in the grid is always empty.
If I debug I'm seeing the data object returned by useQuery is undefined.
The web request does successfully complete and I can see the data being returned in the network tab under requests in the browser.
I'm suspecting its the way the getAllProducts is structured perhaps or an async await issue but can't quite figure it out.
Can anyone suggest where IO may be going wrong please?
Simply use like this
At first data is undefined so mapping undefined data gives you a error so we have to use isLoading and if isLoading is true we wont render or map data till then and after isLoading becomes false then we can render or return data.
export const getAllProducts = async (): Promise<Product[]> => {
const productEndPoint = 'http://localhost:5000/api/product';
const res= await axios.get(productEndPoint);
return res.data as Array<Product>;
};
const { data:products , isLoading } = useQuery('products', getAllProducts);
if(isLoading){
return <FallBackView />
}
return (){
products.map(item => item)
}
I have managed to get this working. For the benefits of others ill share my learnings:
I made a few small changes starting with my api function. Changing the function to the following:
export const getAllProducts = async (): Promise<Product[]> => {
const response = await axios.get(`api/product`, {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
return response.data as Product[];
};
I do not de-construct the response of the axios call but rather take the data object from it and return is as an Product[]
Then second thing I then changed was in my ProductTable component. Here I told useQuery which type of response to expect by changing the call to :
const { data } = useQuery<Product[], Error>('products', getAllProducts);
Lastly, a rookie mistake on my part: because I was using a mock api in a docker container running on localhost and calling it using http://localhost:5000/api/product I was getting the all to well known network error:
localhost has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present...
So to get around that for the purpose of this exercise I just added a property to the packages.json file: "proxy":"http://localhost:5000",
This has now successfully allowed fetching of the data as I would expect it.

How can I optimize my code to stop sending GET requests constantly?

I am using the Yelp Fusion API to get a list of restaurants from Yelp. However, I am always constantly sending a GET request and I am not sure what is going on or how to fix it. I have tried React.memo and useCallback. I think the problem lies within how I am making the call rather than my component rerendering.
Here is where I send a GET request
// Function for accessing Yelp Fusion API
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
// Saving our results, getting first 5 restaurants,
// and turning off our loading screen
setYelpResults({businesses: response.data.businesses.splice(0, 5)});
setEnableLoading(1);
}
catch (error) {
setEnableLoading(2);
}
};
This is where I use axios.
// Our Yelp Fusion code that sends a GET request
export default axios.create({
baseURL: `${'https://cors-anywhere.herokuapp.com/'}https://api.yelp.com/v3`,
headers: {
Authorization: `Bearer ${KEY}`
},
})
You are probably calling that function within your functional component and that function sets a state of that component, so it re-renders. Then the function is executed again, sets state, re-renders and so on...
What you need to do is to wrap that API call inside a:
useEffect(() => {}, [])
Since you probably want to call it one time. See useEffect doc here
You can do 2 things either use a button to get the list of restaurants because you are firing your function again and again.
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
Use a button instead maybe so once that button is clicked function is fired.
<button onClick={yelpFusionSearch} />Load More Restaurants </button>
Use your fuction inside useEffect method which will load 5 restaurants once the page renders
useEffect(() => {
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
}, [])

why Axios promises doesn't get executed in order in React

useEffect(() => {
if(reviewsData) {
reviewsData.forEach( item =>
{
if(item)
{
item.text.forEach((review,i) =>
{
axios({
method: "post",
url: "http://localhost:5000/retrieveuserbyid",
data: {
userId: item.user
},
headers: {
authToken: localStorage.getItem("authToken"),
"Content-Type": "application/json"
}
}).then(res => {
const name =res.data.name;
setTempUser([...tempUser , name]);
tempUser2 = [...tempUser2, name];
console.log(name);
console.log(tempUser2);
}).catch(err => {
});
}
)
}
}
);
}
}, [reviewsData]
);
reviewsData.text are comments people post on my site and I want to retriev username for each person leaving comments. (store them into a array initially) but I don't understand why in the state tempUser I have only the last user and in the tempUser2 I have all the users but in a random order each time I refresh. *One person can leave multiple comments.
React state updates are asynchronous. When you queue up all the axios promises the current value of tempUser is enclosed and used for all the state updates. If you use a functional state update, however, they can be correctly queued up and processed. Functional state updates allow each queued update to access the previous update, i.e. each updated depends on the previous one.
then(res => {
const name =res.data.name;
setTempUser(tempUser => ([...tempUser , name]));
tempUser2 = [...tempUser2, name];
console.log(name);
console.log(tempUser2);
})
Side note: Axios is capable of concurrent fetches (axios.all), and since axios uses promises, you can also map a bunch of requests and use Promise.all, which returns an array of all resolved axios requests, in the order they were queued up (if order matters).

React - set localstorage with axios data before continuing

am having a small challenge setting up a proper initialization for my react app.
Having some settings in localstorage, I'd want to populate them with the data coming from an axios get request, before ANYTHING else in the app happens (e.g. initialization of the rest of the constructor lines).
What happens currently is that whilst the line executes, the code continues and reads the 'old' localstorage, which is not yet updated:
APP.JS
...
this.readSettingsDB(false);
this.state = {
chartTimeAggregationType: JSON.parse(localStorage.getItem('timeAggregationType')) // <-- This value is still old
dataLoading: true,
...
readSettingsDB(refreshPage) {
data = axios.get('/someurl').then(response => {
localStorage.setItem('timeAggregationType': reponse.time)
});
}
Where are you using refreshPage? Here is how I would handle your issue.
readSettingsDB = async (refreshPage) => { // arrow function make async
const data = await fetch('/someurl'); // use fetch
const response = await data.json();
localStorage.setItem('timeAggregationType': reponse) // set storage
});
}
If you want to setState first, setState comes with a callback.
readSettingsDB = async (refreshPage) => { // arrow function make async
const data = await fetch('/someurl'); // use fetch
const response = await data.json();
this.setState({
timeAggregationType: reponse
}, () => {
localStorage.setItem('timeAggregationType': this.state.timeAggregationType) // set storage
});
})
}

Resources