Infinite loop using fetch() in a custom react hook - javascript

I am making my first custom hook,for now it should just send a GET request. This is the hook:
const useMyCustomHook = request => {
// I have a couple of useState() here
const sendRequest = async () => {
// I set the states here
try {
console.log("2) This log shows up.");
const response = await fetch(request.url);
console.log("This does NOT shows up.");
if (!response.ok) throw new Error('Something went wrong');
// I reset the states here
} catch(error) {
console.log(error);
}
}; // end sendRequest()
console.log("1) This log shows up.");
sendRequest();
console.log("3) This log shows up.");
return {infoFromTheStates}
};
And that's how I call it: (The actual url is a bit different)
const response = useSendRequest({url: 'https://react-http-b228c-default-rtdb.europe-west1.firebasedatabase.app/tasks.json'});
When I run the app I get an infinite loop and I get the logs in the order I named them: first I get 1) then 2) and 3), then again 1) and so on...
It's like I never arrive to get a response from fetch().
I don't even know if the problem is my understanding of async/await or of some React logic.
Anyone knows what's going on?

Each time the component runs, it calls this custom hook, which makes a fetch and then change the state, which makes the component run and it calls the hook and so on.
You need to use useEffect so that it will only run the fetch once:
change
sendRequest();
to:
useEffect(sendRequest, []);

Related

How to tell React Query fetchQuery to make a new GET request and not used the already cached response?

I have the following function that makes a GET request for my user information and caches it using react query's fetchQuery so that every call after the first one does not make a GET request and instead just pulls the data from the cache.
export const getVegetables = async () =>
await queryClient.fetchQuery(['getVegetables'], async () => {
try {
const { data } = await request.get('/vegetables');
return data;
} catch (error) {
throw new Error('Failed to fetch vegetables');
}
});
The problem is that now I actually want to make a new GET request in order to check if the user data has changed, but calling getVegetables() pulls from the cache. How can I instruct fetchQuery to make a fresh GET request and not used the cache?
A slight modification to your function will allow you to first invalidate the query (which will remove it from the cache).
export const getSelf = async (skipCache = false) => {
if(skipCache) { queryClient.invalidateQueries(['getSelf']); }
return queryClient.fetchQuery(['getSelf'], async () => {
try {
const { data } = await request.get('/users/me');
// #todo: This sideloads a bunch of stuff, that we could cache
return data;
} catch (error) {
throw new Error('Failed to fetch user information');
}
});
}
In case of using fetchQuery, you can set cacheTime to 0 in query options, so every time you call it, it will suggest that cache is outdated and fetch fresh data, but I'd suggest you to use useQuery.
Here you can read about difference between useQuery and fetchQuery
The best way is to use useQuery hook and invalidate that query.
import { useQueryClient } from '#tanstack/react-query'
// Get QueryClient from the context
const queryClient = useQueryClient()
queryClient.invalidateQueries({ queryKey: ['getSelf'] })
After invalidation, it will immediately fetch fresh data.
fetchQuery will always fetch unless there is data in the cache that is considered fresh. This is determined by the staleTime setting.
staleTime defaults to 0 - which means "immediately stale". So the code you are showing that is calling fetchQuery should always fetch - unless you have a global staleTime set. You're not showing this in your code, but I guess this must be the reason. Note that fetchQuery doesn't know about staleTime being set by other observers (created by useQuery).
Now if you have a globally set staleTime and that is affecting fetchQuery, but you still want to always fetch, the best thing you can do is pass staleTime: 0 directly to fetchQuery. Again, this is the default behaviour, so it's only necessary if you have a global staleTime set:
await queryClient.fetchQuery(
['getSelf'],
async () => { ... },
{ staleTime: 0 }
)

Why I get three undefined things and why there are two outputs in one call?

I'm new to react. So I'm developing a application which gets data from a api. So I used this piece of code to get data from a api.
let [jsonData,setJsonData]=useState([]);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(apiURL);
const json = await response.json();
setJsonData(json.components.map(function(item){
return item;
}))
} catch (error) { }
}
fetchData();
}, [])
const [table1,table2,pieChart]=jsonData;
console.log(table1,table2,pieChart)
This is the problem. When I run this I get a output like this.
In this why there are two outputs in the first call and in first output why I get 3 undefined things.I just need to get the only those JSON data in the first call.How do I get the required data only and not to get those undefined things.
Thanks in advance.
You can't avoid the fact that your fetch function is asynchronous, but you can do something like this:
if (!jsonData.length) return <div>loading...</div>;
You are doing an asynchronous request in your useEffect function, which means that your fetch function will run parallel to the current code but it will finish sometime after. So the first time you console.log those values the useEffect has not finished yet. At the end of your useEffect you used the setJsonData, which will make the component re-render with the updated state, hence this is why you see the second console.log with the correct values at last.

Access Token in React.useEffect generally works, but comes back undefined when page is refreshed / reloaded

I have a simple React page / component in Gatsby that makes an API call. For this API call I need a token. I use gatsby-theme-auth0 to obtain this token via their AuthService object.
I am starting the API call in my useEffect. It looks like this:
useEffect(() => {
//defining the async function
async function fetchFromAPI() {
try {
const data = await fetchData()
setData(data)
}
}
//executing the async function:
fetchFromAPI()
}, [])
The function fetchData(), which is asynchronously called in useEffect currently looks like so:
async function fetchData() {
const client = new GraphQLClient(SERVER_URL_GRAPHQL)
let aToken = await AuthService.getAccessToken()
client.setHeader('authorization', `Bearer ${aToken}`)
const query = ...
const data = await client.request(query)
return data
}
All of this generally works. When I navigate to this page, from a different page within my SPA it works. However, when I reload the page, it doesn't. the access token (aToken) then comes back as undefined.
But: I can make things work, when I wrap a setTimeout around the whole call. Then the access token comes back fine and isn't undefined. So I guess something first needs to initialise before AuthService can be called? I'm just not sure how to ensure this.
But this is not what I want to do in production. Now I am wondering why this is. Maybe I am using useEffect the wrong way? Unfortunately, I have not been able to find anything online or on github so far. I'm sure the problem is rather basic though.
EDIT: The AuthService.getAccessToken() method can be found here It's part of gatsby-theme-auth0
EDIT: To clarify, the server does receive the request and sends back {"error":"jwt malformed"} - which makes sense, since it's undefined.
I don't know if you have the authentication in a hook already or not, but you need to check if the user is authenticated before you make any api call, especially those that on app init. Do you have a hook/context when you handle the authentication ? If you have, you can change your code a bit
const {isAuthenticated} = useContext(userAuthenticatedContext)
useEffect(() => {
//defining the async function
async function fetchFromAPI() {
try {
const data = await fetchData()
setData(data)
}
}
//executing the async function:
if(isAuthenticated) fetchFromAPI()
}, [isAuthenticated])
This way, isAuthenticated is a dependency in your useEffect and it will run again when the value of isAuthenticated is changed and it will not fail as you are doing a check, before making the call.
getAccessToken relies on that modules' this.accessToken value to be set. It looks like you need to call either handleAuthentication or checkSession prior to making your call so that the value gets initialized properly. Consider putting checkSession somewhere that runs when the page loads.

Updating State In React With React Hooks

I've been having a difficult time updating state inside of my React application lately using the useState hook.
If I define my state in a provider as -
const [session, setSession] = useState({});
const [sessionId, setSessionId] = useState(0);
And then try to set it using the setSession
setSession(response.data);
It always comes back as the default value. This all happens inside of the provider component - i.e. I'm trying to access the information within other functions in that same provider.
However, if I store that data in localStorage, for example, I have no issues accessing it whatsoever.
localStorage.setItem("session", JSON.stringify(response.data));
I've verified that the information coming from the server is an object, and that the correct data. There's no errors or promises, just the object containing the response. If I put the snippet the setSession(response.data) and localStorage.setItem("session", JSON.stringify(response.data)) next to each other, the setSession leaves the session value as {} whereas setting the local storage works perfectly. Both are using the same API response, same data
// This is the method on my component that I'm trying to use to update the state
const updateStateAfterSessionInitialization = async data => {
setSession(data)
localStorage.setItem("session", JSON.stringify(data));
setSessionId(data.id);
// both of these log a value of `{}` and `0` despite the values being set above
console.log(session)
console.log(sessionId)
closeStartSessionModal();
// If I redirect my application like this, it works fine. The ID is the correct value being returned by the server
window.location = "/#/reading-sessions/" + data.id;
}
// All of this code below is wrapped in a function. None of this code is being executed at the top level
let response = await axios({
method: method,
data:data,
url: url,
headers: headers
});
await updateStateAfterSessionInitialization(response.data);
Literally all of the data is working perfectly fine. The server responds with the correct data, the correct data is stored the session in local storage. If I redirect using the ID from the object from the server, it works fine. But if I try to update the state of the component and access the state properly, it just just doesn't work, and as a result I'm having to try to find ways of working around setting the state.
Is there something that I'm misunderstanding here?
The code that I'm working with is here - https://github.com/aaronsnig501/decyphr-ui/commit/ef04d27c4da88cd909ce38f53bbc1babcc3908cb#diff-25d902c24283ab8cfbac54dfa101ad31
Thanks
The misunderstanding you have here is an assumption that state updates will reflect immediately which is incorrect
State update is async and will only refect in the next render cycle. If you try to update state and log it in the next line, you wouldn't see and updated state
// This is the method on my component that I'm trying to use to update the state
const updateStateAfterSessionInitialization = async data => {
setSession(data)
localStorage.setItem("session", JSON.stringify(data));
setSessionId(data.id);
// both of these log a value of `{}` and `0` despite the values being set above
console.log(session) // This is expected to log previous value
console.log(sessionId) // This is expected to log previous value
closeStartSessionModal();
window.location = "/#/reading-sessions/" + data.id;
}
Now localStorage is synchronous and hence its update is reflected immediately
If you wish to see if the update to state was done correctly you could write a useEffect that depends on it
useEffect(() => {
console.log('State session/sessionId updated');
}, [session, sessionId])
Now depending on what you are trying to achieve you would need to modify your code in line with the above statement that state update calls are asynchronous.
Also setSession doesn't return a promise so you can't just use async await with it. You need to make use of useEffect to take an action on state update
For Example:-
import React, { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios("https://jsonplaceholder.typicode.com/posts");
console.log(result.data);
setData(result.data);
};
fetchData();
}, []);
return (
<ul>
{data.map(res => (
<li>{res.title}</li>
))}
</ul>
);
}
export default App;
Check your type of response.data and defined the types
array - []
objects - {}
string - ""
number - 0 in useState
setState is async, so new value will apply on the next rerender not in the next line.

Sleep function for React-Native?

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
)
);
}

Categories