React repeatedly requests API data - reactjs

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.

Related

Axios API call returns a 404 on page render, but still returns api objects in terminal

I am using axios to make an api call to an api found on Apihub for a next JS app.
here is the code for the function to make the call to provide a list of property JSON objects.
export const baseUrl = "https://zillow56.p.rapidapi.com"
export const fetchApiListsingsCustom = async (url) => {
const { data } = await axios.get((url), {
method: 'GET',
headers: {
'X-RapidAPI-Key': '328713ab01msh862a3ad609011efp17e6b4jsn0e7112d5ee9a',
'X-RapidAPI-Host': 'zillow56.p.rapidapi.com'
}
});
data.then((res) => {
console.log(res);
})
.catch((error) => {
console.error(error);
});
return data.json();
}
When rendering the page I'm attempting to inject the response's data to dynamically create a list of apartment listings.
I'm trying to use getServerSideProps so that the data is already available by the time a user requests the page. After fetching the data, I want to also print them in the terminal to validate it's success.
export default function Home({ propertiesCustomdata })
export async function getServerSideProps() {
const propertiesCustom = await fetchApiListsingsCustom(`${baseUrl}`)
const propertiesCustomdata = propertiesCustom.json()
return {
props: {
propertiesCustomdata
}
}
}
The problem is, I seem to be getting a 404 error from the axios call, before the page gets a chance to load. When I access this I get a 404 error but I also manage to receive some contents of the call the API was to make.
My apologies if this is unclear, but this is all I know to report on this so far.
Studying async and await, fetch, and axios. Very confusing.

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.

Test custom hook with multiple async requests with jest and renderhook

I've been working on an application that uses react-query for handling and caching responses to our backend. Got a lot of inspiration from the creator behind that framework to use custom hooks for handling backend states. In the beginning it was quite easy but later on the hooks started to be dependent to one and another. This resulted in some wierd behavior in the CI were some testcases failed due to heavy load on the servers. (note that backend is mocked with msw)
Custom hook containing multiple custom hooks:
const useFooMutation = () => {
const cache = useQueryClient();
const { types } = useTypes(); // async call to backend
const { user } = useAuth(); // async call to backend
return useMutation(['key'], mutationFunction, {
onSuccess: async (data, variables) => {
// Do cache handling by using values types and user
},
});
};
Test case:
test('useFooMutation should foo', async () => {
const { wrapper } = setupCache();
const { result, waitFor, waitForNextUpdate } = renderHook(() => useFooMutation(), { wrapper });
await waitForNextUpdate();
act(() => {
result.current.mutate({
values
});
});
await waitFor(() => result.current.isSuccess && !result.current.isLoading);
expect(result.current.isSuccess).toEqual(true);
expect(result.current.data).toEqual(fooMockData);
});
When I debugged the code I saw that sometimes the values from the hooks useTypes and useAuth was not yet fetched. The values from the other custom hooks are used when I do cache manipulation. So this results in some wierd behavior for some machines. For example running locally this runs ok most of the times but on a heavy loaded CI server, it results in crashes quite often.
What I've tried so far is to add a timeout before the mutation function in the test case on a timeout that seems to work, not a nice solution. And by using the waitForNextUpdate the test case works fine most of the times but not when the CI is loaded.
So I need a good way to find out when then the useFooMutation has gathered all values from the hooks its dependent on before I can call mutate()?

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

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

When to use Redux to fetch data from api call

I adopted Redux in my project for state control, and also Axios for fetching api in action.
But I wonder when should I fetch data using API call through Redux (in action), when should I directly make the api call in component.
Is it depending on, whether I need to store the response data in Redux (for sharing among different components)? May I know any best practice for it?
API call through Redux
export const fetchOptions = () => {
return async (dispatch, getState) => {
const request = await client.query({
query: gqlQueries.getQuery,
});
const options = await request;
dispatch({
type: types.FETCH_DATA_END,
options: options
});
}
}
Directly make API call in component:
const axios = require("axios");
useEffect(() => {
axios({
url: 'http://localhost/graphql',
method: 'post',
data: {
query: `
query PostsForAuthor {
author(id: 1) {
firstName
posts {
title
votes
}
}
}
`
}
}).then((result) => {
console.log(result.data)
});
}, []);
If multiple components are using the same data, redux shines there. API calls in components are preferred when you do not want any stale data to show, therefore you call api every time component mounts and your data is always in sync with your back end. There might be some other criteria but these two help me decide , where to keep the state.

Resources