So i’ve had a long look at this and have not been satisfied with any of the hacky solutions found online nor have I found a solution myself.
Context:
React 18 has added a mount -> unmount -> re-mount behaviour to its components to help detect impurity in component’s code. This has sparked a lot of confusion amongst developers and I’d like to get to the bottom of a certain use case that’s talked about too little.
Problem
For most cases things can be pure and there is therefore no problem with the behavioural choice. However some tasks are impure by nature and it seems the industry has settled on some hacky stuff that requires adding more complexity and more npm library usage to get around the issue.
Example
For my use case I am trying to handle a very common auth flow with authorization code to be send to my private API in order to retrieve a token. Now this authorization code is one time use only and the server will rightfully reject any further attempts to get an access tokens from it.
This means that this task is impure by nature and nothing can be done about that.
The actual logic goes as follow:
- The app loads with it’s initial redux state and performs a get request to get an access token from a private proxy api looking for an httpOnly refresh token cookie.
- If that request fails the user is directed to the login page otherwise he will remain on the current, private, page
- The login page checks for a
code
query param. If present it will make a post request to exchange it for an access token and a new refresh token. If not present it will redirect the user to the Auth provider hosted login UI for the user to input his credential. That hosted login UI will then redirect the user to my login page with acode
query param.
Solutions found online
I’ve come across multiple solutions while trying to solve this issue but have not be satisfied with any of them. I will explain shortly and I would like to find an actually solution to this issue if possible rather than the following bandaid solutions.
Solution 1: Third party libraries
Use one of these fetching libraries (React Query, useSWR) to manage caching and request deduping.
This is the "recommended" solution yet this feels completely off to me as you’d basically be just patching the issue and adding MB to your bundle. At the end of the day you’re not making that functionality pure so this is actually going against React dev’s recommendation of not trying to prevent multiple calls but rather trying to make it not matter. Problem is you cannot make it not matter in this case because, again, that auth code is rejected if used more than once.
Solution 2: useRef
You can make use of a ref to handle a local state and check for its value to handle whether to do the token exchange call or not.
Again this is just a hack although this is by far, to me, the cleanest solution. Complexity is kept low and bundle size is not increase. Only downside is it seems non intuitive and confusing which requires a deeper react knowledge from the developers and is therefore a maintainability issue.
Solution 3: Caching the response
This seems like a security issue as you now are able to get/steal an access token by duplicating the request.
Solution 4: Removing strict mode
Seems very wrong but actually given a certain developer quality is maintained this might be the best solution, in my mind. At the end of the day strict mode double rendering is a paradigm choice made by the react dev’s which isn’t without its issues. Biggest of which is that you now have a dev environment that works differently than your prod environment.
But this also means removing a safety net that is quite handy.
Conclusion
I have not found a satisfying solution although I can think of one that could be developed. Maybe it already exists and I’m not aware of it. There basically needs to be a standard, such as a hook, to run code that is impure by nature. I can easily make a hook out of Solution 2 for that purpose but maybe there is something I didn’t think of. Maybe somewhere I can place the token exchange logic that makes it not trigger twice.
Your input would be most welcome. For now I will pick Solution 2 as this is the least permanent solution.
2
Answers
Sounds like you looked at what strict mode does, and decided you don’t want to adhere to the general recommendations for pure functions, are aware of the trade-offs and want to make an exception.
There’s a very easy solution to this, just introduce global state. You don’t need any hooks for this, you can just declare a variable outside your component:
You can also just move the logic outside the component and cache the result there.
Given your problem, this is a reasonable solution. The goal of keeping state inside components is to maintain that purity, once you decide you don’t need it things actually get a lot simpler.
I tend to handle this via the following steps in the main React application class:
Usually after a login you want to restore the pre login location. Eg if the user bookmarked a
/mypath
location and returned to it when they were no longer authenticated, the app might store that path in local storage before the login redirect, then restore it upon return, as above.Even if you don’t want to support deep links, then this avoids leaving unsightly OAuth response parameters visible to the user. It is similar to how website tech stacks rewrite the URL after a login. Perhaps it also avoids the re-entrancy issue you mention, since UI views such as the above class will be recreated occasionally.
I classify this as
page load logic
that is independent of the SPA tech stack. So eitheruseState
oruseRef
might do the job. Once the logic has completed, the main SPA runs.Not sure if this code is useful, and the second link is complicated by the fact that I’m trying to manage logins in a shell micro UI. Perhaps it provides an idea or two though.