kinde-oss / kinde-auth-react

Kinde React SDK for frontend authentication flows
https://docs.kinde.com/developer-tools/sdks/frontend/react-sdk/
MIT License
50 stars 11 forks source link

Bug: Client is undefined in getToken function #53

Open pwaltron opened 3 months ago

pwaltron commented 3 months ago

Prerequisites

Describe the issue

We have noticed, usually after a refresh of our application, an error being logged to the console from the SDK;

TypeError: Cannot read properties of undefined (reading 'getToken') at KindeProvider.tsx:176:1

Is appears that client is undefined when we call the getToken function. To get around this we are using getToken within a useEffect hook essentially until we get a returned value. This issue is compounded by https://github.com/kinde-oss/kinde-auth-react/issues/45 as the promise is rejected with an undefined error due to being console logged in the sdk directly.

Additionally, we have noticed that the getToken function is declared in kinde-auth-pkce-js/index as having 0 arguments however one argument is being passed in at KindeProvider.tsx:176. It doesn't look like this causes the above issue, but it was a warning from our IDE

Library URL

https://github.com/kinde-oss/kinde-auth-react

Library version

4.0.1

Operating system(s)

macOS

Operating system version(s)

Sonoma 14.0

Further environment details

No response

Reproducible test case URL

No response

Additional information

No response

simonszalai commented 3 months ago

I encountered the same problem with getUser()

peterphanouvong commented 3 months ago

Hey guys, sorry you're experiencing these issues.

We're in the process of cleaning up the SDKs, thanks so much for raising the issue - we'll try make it better for you

pwaltron commented 3 months ago

@peterphanouvong

The below is a code excerpt from a context provider in my application, await getToken() fails on the first call of the useEffect hook, but this is recalled by other logic in the application until setToken() is called with a valid token from getToken()

useEffect(() => {
        async function getUserAndToken() {
            if (!app.currentUser) {
                try {
                    let kindeToken = await getToken();
                    setToken(kindeToken)
                } catch (e) {
                    console.log('Error during login:', e);
                }
            }
        }

    getUserAndToken();

    }, [app, getToken]);

Thanks

simonszalai commented 3 months ago
peterphanouvong commented 3 months ago

Thanks @pwaltron and @simonszalai - we're going to try and recreate the issue and solve it for you ASAP :)

peterphanouvong commented 3 months ago

Hey @pwaltron and @simonszalai, thank you for your patience and for raising the issue. I've put in a fix to 3.0.29-beta.2

A couple things we will need to handle on our side and then we can get it done as a proper release, but for now hopefully this works for you :)

peterphanouvong commented 3 months ago

Should be fixed as a part of the release 4.0.2

simonszalai commented 3 months ago

@peterphanouvong Thank you for the fix! Could you provide an example of how we should be using this hook? Because right now, in the hook

const { getToken } = useKindeAuth()

The getToken function can be undefined. Currently I am using this function to call my API with the Kinde token:

export const useFetchApi = () => {
  const { getToken } = useKindeAuth()

  const fetchApi = async <T>(url: string, schema: z.ZodSchema<T>): Promise<T> => {
    const kindeToken = await getToken?.()
    const res = await fetch(url, {
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json',
        Authorization: `Bearer ${kindeToken}`,
      },
    })

   // ... More irrelevant code
  }
  return fetchApi
}

On the first render, kindeToken is undefined, because the function itself is undefined. I understand that getting the token is an async op and somehow it needs to be awaited, but I'm sure there must be a better solution than this one. Could you help me out?

Thank you!

peterphanouvong commented 2 months ago

Hey @simonszalai, the getToken function will be undefined while useKindeAuth is loading. I think your best bet right now is to check isLoading from useKindeAuth before calling the getToken function, or storing the kindeToken somewhere you can grab it synchronously :)

simonszalai commented 2 months ago

Thank you, that makes sense. I came up with this hook that seems to work reasonably well, posting it here in case someone else encounters the same issue:

export const useKindeToken = () => {
  const { getToken, isLoading } = useKindeAuth()
  const [kindeToken, setKindeToken] = useState<string | null>(null)
  const isLoadingRef = useRef(isLoading)

  useEffect(() => {
    isLoadingRef.current = isLoading
  }, [isLoading])

  const fetchToken = useCallback(async (): Promise<string> => {
    if (kindeToken) return kindeToken

    while (isLoadingRef.current || !getToken) {
      await new Promise((resolve) => setTimeout(resolve, 100))
    }

    const token = await getToken()
    invariant(token, 'Failed to retrieve token')
    setKindeToken(token)
    return token
  }, [getToken, kindeToken])

  return fetchToken
}

NOTE: using a ref because otherwise isLoading would get stuck in a closure and never change to false