feat[client-js]: Adds React Router v8 sample app for @okta/okta-react-client-js - #322
Conversation
| { | ||
| path: '/resource', | ||
| element: <Resource />, | ||
| loader: createFetchLoader(fetchClient, () => userInfoUrl), |
There was a problem hiding this comment.
I think using the userInfo URL is a bit confusing. It's an OAuth endpoint, so a developer should use OAuth2 to request it, not a FetchClient. Maybe instead use a static JSON blob and can be hosted via vite to illustrate this?
| // ever reset it on completion or failure, neither of which runs for an abandoned redirect. That stuck | ||
| // state makes every later `orchestrator.getToken()` call throw `flow already in progress` immediately. | ||
| // `pageshow`'s `persisted` flag is the standard signal for a bfcache restore, so reset here. | ||
| window.addEventListener('pageshow', (event) => { |
There was a problem hiding this comment.
Was an issue you noticed or something Claude came up with?
There was a problem hiding this comment.
I ran into it by hitting the back button in the middle of the sign in flow, and could not sign in again after that. This is Claude's explanation and fix for it.
| import { orchestrator, signOutFlow } from './auth'; | ||
|
|
||
| const Home: React.FC = () => { | ||
| const [credential, setCredential] = React.useState<Credential | null>(null); |
There was a problem hiding this comment.
What is the purpose of storing a Credential instance on the App State? I think the only reason you should do this is if you are rendering the token
| export const router = createBrowserRouter([ | ||
| { | ||
| path: '/', | ||
| element: <Home />, | ||
| errorElement: <ErrorBoundary />, | ||
| }, | ||
| { | ||
| path: '/protected', | ||
| element: <Protected />, | ||
| loader: createTokenLoader(orchestrator), | ||
| errorElement: <ErrorBoundary />, | ||
| }, | ||
| { | ||
| path: '/resource', | ||
| element: <Resource />, | ||
| loader: createFetchLoader(fetchClient, () => '/resource.json'), | ||
| errorElement: <ErrorBoundary />, | ||
| }, | ||
| { | ||
| path: '/login/callback', | ||
| element: <LoginCallback />, | ||
| loader: createLoginCallbackLoader(orchestrator), | ||
| errorElement: <ErrorBoundary />, | ||
| }, | ||
| ]); |
There was a problem hiding this comment.
| export const router = createBrowserRouter([ | |
| { | |
| path: '/', | |
| element: <Home />, | |
| errorElement: <ErrorBoundary />, | |
| }, | |
| { | |
| path: '/protected', | |
| element: <Protected />, | |
| loader: createTokenLoader(orchestrator), | |
| errorElement: <ErrorBoundary />, | |
| }, | |
| { | |
| path: '/resource', | |
| element: <Resource />, | |
| loader: createFetchLoader(fetchClient, () => '/resource.json'), | |
| errorElement: <ErrorBoundary />, | |
| }, | |
| { | |
| path: '/login/callback', | |
| element: <LoginCallback />, | |
| loader: createLoginCallbackLoader(orchestrator), | |
| errorElement: <ErrorBoundary />, | |
| }, | |
| ]); | |
| const tokenLoader = createTokenLoader(orchestrator); | |
| const fetchLoader = createFetchLoader(fetchClient); | |
| const callbackLoader = createLoginCallbackLoader(orchestrator); | |
| export const router = createBrowserRouter([ | |
| { | |
| path: '/', | |
| element: <Home />, | |
| errorElement: <ErrorBoundary />, | |
| }, | |
| { | |
| path: '/protected', | |
| element: <Protected />, | |
| loader: tokenLoader, | |
| errorElement: <ErrorBoundary />, | |
| }, | |
| { | |
| path: '/resource', | |
| element: <Resource />, | |
| loader: fetchLoader('/resource.json'), | |
| errorElement: <ErrorBoundary />, | |
| }, | |
| { | |
| path: '/login/callback', | |
| element: <LoginCallback />, | |
| loader: callbackLoader, | |
| errorElement: <ErrorBoundary />, | |
| }, | |
| ]); |
…/client-js Exercises the opt-in loader factories (createFetchLoader, createTokenLoader, createLoginCallbackLoader) end-to-end against a real Okta org, and resets the sign-in flow's stuck `inProgress` state on a back/forward-cache restore so an abandoned redirect doesn't block starting a new one. Co-Authored-By: Claude Code
…rinfo endpoint Hand-building the userinfo URL bypassed the SDK's own OAuth2Client/Credential userInfo() methods, which resolve the endpoint from OIDC discovery metadata. Swap the sample's /resource route to a plain static JSON file so it just demonstrates createFetchLoader without duplicating SDK logic.
…m vite.config.js A clean build doesn't reproduce this react-router warning - it was vestigial from an earlier scaffolding iteration. Restore the unconditional onwarn throw, matching test-harness-app's convention.
createFetchLoader/createTokenLoader now bind to their client/orchestrator up front and return a helper you call from within your own loader, rather than being usable as a loader directly. Adopts createLoadersFromOrchestrator for the token/login-callback pair, per the updated README's wiring example.
7457171 to
e912e08
Compare
Home never renders the token - it only needs a signed-in/signed-out boolean to pick a button, and idToken to end the Okta session. Holding the Credential object itself in state has no purpose here and risks going stale (e.g. after signOut's credential.remove()); fetch it fresh via Credential.getDefault() at the point of use instead.
Exercises the opt-in loader factories (createFetchLoader, createTokenLoader, createLoginCallbackLoader) end-to-end against a real Okta org, and resets the sign-in flow's stuck
inProgressstate on a back/forward-cache restore so an abandoned redirect doesn't block starting a new one.PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
Issue Number: N/A
What is the new behavior?
Does this PR introduce a breaking change?
Other information
Reviewers