Help with race conditions/react double request
Answered
Toy Fox Terrier posted this in #help-forum

Toy Fox TerrierOP
Hey there,
I am looking for help to deal with what I think is race conditions or perhaps the react 18 useEffect system of where it makes two requests when you've only coded for one. This is affecting an integration I am working on to connect a user with their spotify account. It sends the request for a token and code challenge and I can see in the logs that this first auth flow succeeds with a 200 but then it fires off a second one simultaneously (slightly after) that naturally returns a 400 because the first code challenge has already been used (basically, refreshing a token twice but with the same token). If I perform all of this using "use client" in all relevant components then it doesn't double send, but if I remove "use client" it will cause the double request.
I am aware I am not showing any code here and will happily show code but I am unsure what is relevant right now and don't want to bombard with tonnes of code.
Structure:
page.tsx (SSR) -> DashboardContent.tsx (SSR) -> SpotifyCardWrapper.tsx (CSR) -> SpotifyAuth.tsx (CSR)
If I change DashboardContent.tsx to CSR then it sends one request, if I leave it as SSR it makes two (a 200 then a 400)
I am looking for help to deal with what I think is race conditions or perhaps the react 18 useEffect system of where it makes two requests when you've only coded for one. This is affecting an integration I am working on to connect a user with their spotify account. It sends the request for a token and code challenge and I can see in the logs that this first auth flow succeeds with a 200 but then it fires off a second one simultaneously (slightly after) that naturally returns a 400 because the first code challenge has already been used (basically, refreshing a token twice but with the same token). If I perform all of this using "use client" in all relevant components then it doesn't double send, but if I remove "use client" it will cause the double request.
I am aware I am not showing any code here and will happily show code but I am unsure what is relevant right now and don't want to bombard with tonnes of code.
Structure:
page.tsx (SSR) -> DashboardContent.tsx (SSR) -> SpotifyCardWrapper.tsx (CSR) -> SpotifyAuth.tsx (CSR)
If I change DashboardContent.tsx to CSR then it sends one request, if I leave it as SSR it makes two (a 200 then a 400)
Answered by B33fb0n3
Solution for SSR:
you can reuse the result from the request when you using
Solution for Client Components:
When you fetch data in client components you should always use a clientside fetching library like SWR or React Query. Like that issues like you have currently are automatically resolved as they cache the results automatically on the client
you can reuse the result from the request when you using
React.cache(() => { your code here })
Solution for Client Components:
When you fetch data in client components you should always use a clientside fetching library like SWR or React Query. Like that issues like you have currently are automatically resolved as they cache the results automatically on the client
14 Replies

@Toy Fox Terrier Hey there,
I am looking for help to deal with what I think is race conditions or perhaps the react 18 useEffect system of where it makes two requests when you've only coded for one. This is affecting an integration I am working on to connect a user with their spotify account. It sends the request for a token and code challenge and I can see in the logs that this first auth flow succeeds with a 200 but then it fires off a second one simultaneously (slightly after) that naturally returns a 400 because the first code challenge has already been used (basically, refreshing a token twice but with the same token). If I perform all of this using "use client" in all relevant components then it doesn't double send, but if I remove "use client" it will cause the double request.
I am aware I am not showing any code here and will happily show code but I am unsure what is relevant right now and don't want to bombard with tonnes of code.
Structure:
page.tsx (SSR) -> DashboardContent.tsx (SSR) -> SpotifyCardWrapper.tsx (CSR) -> SpotifyAuth.tsx (CSR)
If I change DashboardContent.tsx to CSR then it sends one request, if I leave it as SSR it makes two (a 200 then a 400)

Solution for SSR:
you can reuse the result from the request when you using
Solution for Client Components:
When you fetch data in client components you should always use a clientside fetching library like SWR or React Query. Like that issues like you have currently are automatically resolved as they cache the results automatically on the client
you can reuse the result from the request when you using
React.cache(() => { your code here })
Solution for Client Components:
When you fetch data in client components you should always use a clientside fetching library like SWR or React Query. Like that issues like you have currently are automatically resolved as they cache the results automatically on the client
Answer

Toy Fox TerrierOP
Hey @B33fb0n3 thanks so much for your response! I am still trying to wrap my head around SSR vs Client Components as I am sure you can tell.
so my currently auth flow is to use Zustand with the nextjs api. I can call on the zustand store for:
I imagined the useEffect causing trouble here along with using perhaps Zustand. But I felt like I wanted to move state out to a store for things like 'isAuthenticated' etc. and then from the store I make the token calls to the api
so my currently auth flow is to use Zustand with the nextjs api. I can call on the zustand store for:
const searchParams = useSearchParams();
const getAuthUrl = useSpotifyStore((state) => state.getAuthUrl);
const handleAuthCode = useSpotifyStore((state) => state.handleAuthCode);
const clearAuth = useSpotifyStore((state) => state.clearAuth);
const accessToken = useSpotifyStore((state) => state.accessToken);
const isLoading = useSpotifyStore((state) => state.isLoading);
const displayName = useSpotifyStore((state) => state.displayName);
const processedCode = useRef<string | null>(null);
useEffect(() => {
const code = searchParams.get("code");
if (!code || processedCode.current === code) return;
const handleAuth = async () => {
try {
processedCode.current = code;
await handleAuthCode(code);
// Clean up the URL
const currentUrl = new URL(window.location.href);
currentUrl.searchParams.delete("code");
window.history.replaceState({}, "", currentUrl.toString());
} catch (error) {
console.error("Error during authentication:", error);
clearAuth();
processedCode.current = null;
}
};
handleAuth();
}, [searchParams, handleAuthCode, clearAuth]);
const handleLogin = async () => {
try {
const authUrl = await getAuthUrl();
window.location.href = authUrl;
} catch (error) {
console.error("Error starting auth flow:", error);
}
};
I imagined the useEffect causing trouble here along with using perhaps Zustand. But I felt like I wanted to move state out to a store for things like 'isAuthenticated' etc. and then from the store I make the token calls to the api

Toy Fox TerrierOP
I am definitely happy to explore the use of things like react query. I am already using it for other things I assume this is the same one?:
import { useQuery } from "@tanstack/react-query";
import { useQuery } from "@tanstack/react-query";

@Toy Fox Terrier I am definitely happy to explore the use of things like react query. I am already using it for other things I assume this is the same one?:
import { useQuery } from "@tanstack/react-query";

yea, that's react query. You don't use it for this double request thing?

Toy Fox TerrierOP
No so I've only ever come across these double request issues with auth flows. I can see the double request occurring in other parts but for data fetching I'm not too bothered. It's with auth flows that it becomes an issue for me as it causes users to be authenticated and then unauthenticated due to simulataneous requests

@Toy Fox Terrier No so I've only ever come across these double request issues with auth flows. I can see the double request occurring in other parts but for data fetching I'm not too bothered. It's with auth flows that it becomes an issue for me as it causes users to be authenticated and then unauthenticated due to simulataneous requests

why do you handle your auth flow on the client instead of on the server?

@B33fb0n3 why do you handle your auth flow on the client instead of on the server?

Toy Fox TerrierOP
well in this scenario its not really an auth flow I suppose. Users will authenticate with username and password and later on will have the option to 'connect their account to Spotify'. And because I'm still learning nextjs and things like 'use client' etc. I thought I would use Zustand to move all my state over out to global for cleaner code and better management (I used to do this all the time with Vue and Pinia) and using zustand means using client components, therefore my Spotify auth flow involved client rather than server
Also, I have implemented all the necessary changes to move auth flow over to server side and everything works perfectly now thank you! If you'd be interested I do have another challenge I am facing that is definitely of the same vein and is partly due to a lack of my knowledge on the matter but I am having issues with the new authjs library and the headless CMS directus. I think I need to take a proper learning course or something on how to understand server-side vs client side but also in the context of things like authjs. I have discovered there's quite a lot of people that use Directus as a nextjs backend that are running into Auth issues but the main issue comes from just knowing how to use authjs and protecting sites through it etc. How to properly refresh tokens etc. to avoid these race condition scenarios where you get a 200 and then a 400 and therefore the user is logged out because they can't refresh

@Toy Fox Terrier Also, I have implemented all the necessary changes to move auth flow over to server side and everything works perfectly now thank you! If you'd be interested I do have another challenge I am facing that is definitely of the same vein and is partly due to a lack of my knowledge on the matter but I am having issues with the new authjs library and the headless CMS directus. I think I need to take a proper learning course or something on how to understand server-side vs client side but also in the context of things like authjs. I have discovered there's quite a lot of people that use Directus as a nextjs backend that are running into Auth issues but the main issue comes from just knowing how to use authjs and protecting sites through it etc. How to properly refresh tokens etc. to avoid these race condition scenarios where you get a 200 and then a 400 and therefore the user is logged out because they can't refresh

great to see that you now handle your auth securly on the serverside. To handle refreshtokens I would use a middleware, that checks the token and refreshes it if needed. Like that the whole token thing will be ready before the page even loaded

@Toy Fox Terrier Hey there,
I am looking for help to deal with what I think is race conditions or perhaps the react 18 useEffect system of where it makes two requests when you've only coded for one. This is affecting an integration I am working on to connect a user with their spotify account. It sends the request for a token and code challenge and I can see in the logs that this first auth flow succeeds with a 200 but then it fires off a second one simultaneously (slightly after) that naturally returns a 400 because the first code challenge has already been used (basically, refreshing a token twice but with the same token). If I perform all of this using "use client" in all relevant components then it doesn't double send, but if I remove "use client" it will cause the double request.
I am aware I am not showing any code here and will happily show code but I am unsure what is relevant right now and don't want to bombard with tonnes of code.
Structure:
page.tsx (SSR) -> DashboardContent.tsx (SSR) -> SpotifyCardWrapper.tsx (CSR) -> SpotifyAuth.tsx (CSR)
If I change DashboardContent.tsx to CSR then it sends one request, if I leave it as SSR it makes two (a 200 then a 400)

West African Lion
I suggest your problem.
import { useEffect, useRef } from "react";
const SpotifyAuth = () => {
const hasRequested = useRef(false);
useEffect(() => {
if (hasRequested.current) return;
hasRequested.current = true;
fetch("/api/spotify-auth") // Your API request here
.then((res) => res.json())
.then((data) => console.log(data))
.catch((err) => console.error(err));
}, []);
return <div>Authenticating...</div>;
};
import { useEffect, useRef } from "react";
const SpotifyAuth = () => {
const hasRequested = useRef(false);
useEffect(() => {
if (hasRequested.current) return;
hasRequested.current = true;
fetch("/api/spotify-auth") // Your API request here
.then((res) => res.json())
.then((data) => console.log(data))
.catch((err) => console.error(err));
}, []);
return <div>Authenticating...</div>;
};

Toy Fox TerrierOP
Hey @B33fb0n3 I'm really sorry to reopen on the thread here as its related to the original issue but is still a new issue. Is there any chance you'd mind taking a look over my issue on my repo:
https://github.com/Jopgood/directus-t3-authjs/issues/1
I would paste the code/logs here but its a lot to go through. The long/short of it is:
I am struggling to understand why my refresh flow doesn't work when using authjs. In the logs you'll see we refresh the token successfully the first time and generate a new token. But when we refresh again (after about 2 minutes), authjs attempts to refresh the user's session using the original (old) refresh token. Directus then rejects this and the user's session is invalidated. For whatever reason, my code is either not saving the user's refresh token properly or is somehow not using the latest refresh token when refreshing for the second time
https://github.com/Jopgood/directus-t3-authjs/issues/1
I would paste the code/logs here but its a lot to go through. The long/short of it is:
I am struggling to understand why my refresh flow doesn't work when using authjs. In the logs you'll see we refresh the token successfully the first time and generate a new token. But when we refresh again (after about 2 minutes), authjs attempts to refresh the user's session using the original (old) refresh token. Directus then rejects this and the user's session is invalidated. For whatever reason, my code is either not saving the user's refresh token properly or is somehow not using the latest refresh token when refreshing for the second time