I’m using Clerk for authentication, and when I call the logOut method from the useAuth hook, I see a React error in my browser console:
Warning: Can’t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
at AuthProvider
Has anyone else encountered this issue?
I think this code might be triggering the error, but I’m not 100% sure. Perhaps the AuthProvider component should check whether it’s still mounted before calling setState, as demonstrated here?
Could you share how you have the logout used in a page or company so we can reproduce and see a change is needed in the provider or we can also get Clerk to help if a different approach is needed to correct.
Here’s a simplified version of my App.js and the Header component with my logout button. If I can provide any other information that would be helpful, please let me know.
App.js
// I didn't modify this much after running `yarn rw setup auth clerk`.
import { ClerkProvider, withClerk } from '@clerk/clerk-react'
import { AuthProvider } from '@redwoodjs/auth'
import { FatalErrorBoundary, Head, RedwoodProvider } from '@redwoodjs/web'
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
import FatalErrorPage from 'src/pages/FatalErrorPage'
import Routes from 'src/Routes'
import './index.css'
const ClerkAuthConsumer = withClerk(({ children, clerk }) => {
return React.cloneElement(children, { client: clerk })
})
const ClerkAuthProvider = ({ children }) => {
const frontendApi = process.env.CLERK_FRONTEND_API_URL
if (!frontendApi) {
throw new Error('Need to define env variable CLERK_FRONTEND_API_URL')
}
return (
<ClerkProvider frontendApi={frontendApi}>
<ClerkAuthConsumer>{children}</ClerkAuthConsumer>
</ClerkProvider>
)
}
const App = () => (
<FatalErrorBoundary page={FatalErrorPage}>
<RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
<ClerkAuthProvider>
<AuthProvider type="clerk">
<RedwoodApolloProvider>
<Head>
{/* Stylesheet links for Google fonts */}
</Head>
<Routes />
</RedwoodApolloProvider>
</AuthProvider>
</ClerkAuthProvider>
</RedwoodProvider>
</FatalErrorBoundary>
)
export default App
I took another look this morning, and I realized that the React error appears only in my local environment, not on my Vercel preview deployment.
Following your advice, I switched from withClerk to useClerk (and upgraded @clerk/clerk-react for good measure). I still see the React error on my local but not on the preview deployment.
I’m still not 100% what the root issue is, but since it appears to be happening only locally, I’m not too worried.
Heyo - I wrote the original Clerk integration and also ran into this in a project recently so swinging by to see if I can help.
The Bug:
This definitely is a bug. The pieces I’ve identified that are causing this are:
According to their docs, the clerk-js library deletes and recreates the Clerk client as part of logout. (Side note, don’t love this. Would much rather they could just modify the state in place?)
The withClerk HOC and WithClerk component both unmount their children when the Clerk client is not loaded, both at first load and upon sign-out.
The AuthProvider sign-out waits on the sign-out process and then calls setState on the instance of AuthProvider that initiated the signout.
Because of these three pieces interacting, we are seeing this flow where a sign-out deletes the clerk client, which unmounts the App tree below ClerkProvider, which unmounts the first AuthProvider instance. It then makes a new clerk client, which mounts a new App tree, which mounts a second AuthProvider instance. The promise to signout then resolves, which causes the first AuthProvider to try to update its state, which causes this error.
Thinking about a solution:
I had hoped that moving to useClerk in Clerk’s v3 sdk would insta-fix this since that version allows the useClerk hook to return a nullish value if the SDK isn’t loaded rather than throwing. However, this causes an issue where now we’re not reloading enough - since the tree is no longer unmounted, we don’t reload when Clerk’s client reloads or the user state changes. I could fix that by disabling Clerk’s modal-style auth, but that is a rough user experience IMO.
I did finally get it working by doing the above changes and then adding a listener below the AuthProvider that triggers Redwood’s reauthenticate if it detects that Clerk’s auth state has changed. This is as kludgy in terms coding, but does preserve a smoother user experience. Here’s the final App.tsx I wound up with as a github gist. Would appreciate comments on this approach before I PR it as a tweak to the setup-clerk template.
That’s a lot of code to be putting in a user’s App.tsx. If we could tuck most of that away inside @rw/auth though, and just import from there I think this could be a nice solution
Agreed @tobbe and I will take some time this week to sketch out a PR for that.
One thing for discussion: I worry that the more we insert code into the rw/auth package the more we approach a point where we should break that into rw/auth-core and rw/auth-.
E.G. here where we start to really depend on a particular version of clerk-react, we’d want to specify that as a dep or peer dep for people who were using the Clerk auth. However, package.json doesn’t give us a way to specify “conditional” dependencies, like “if you’re using clerk, it has to be this version. if you’re using auth0, this version.” So, we wind up with a choice of: a) a mysterious failure if the end user upgrades their clerk/auth0/whatever outside of our support scope (current situation), b) having to specify all providers as dependencies no matter which you use (non-starter imo), or c) breaking the provider integrations into separate packages so you import only the one(s) you need.
Is this something the core team would be against or is it more in the “if we have time to do this safely that would be fine” category of Big Ideas?
We want to break all the auth provider integrations out in separate packages. Ideally those packages should be maintained by the auth providers themselves, not us in the core team.
After RW v1 is out we want to sit down and think about supporting plugins in RW, where not only auth can be supported as plugins, but also stuff like tailwind/chakra, and more.