Skip to content

Commit

Permalink
fix: beforeLoad arguments, docs: authenticated routes
Browse files Browse the repository at this point in the history
  • Loading branch information
tannerlinsley committed Aug 3, 2023
1 parent 731926b commit 734f642
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 9 deletions.
4 changes: 4 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@
"label": "Router Context",
"to": "guide/router-context"
},
{
"label": "Authenticated Routes",
"to": "guide/authenticated-routes"
},
{
"label": "SSR & Streaming",
"to": "guide/ssr-and-streaming"
Expand Down
79 changes: 79 additions & 0 deletions docs/guide/authenticated-routes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
id: authenticated-routes
title: Authenticated Routes
---

Authentication is an extremely common requirement for web applications. In this guide, we'll walk through how to use TanStack Router to build protected routes, and how to redirect users to login if they try to access them.

## The `route.beforeLoad` Option

The `route.beforeLoad` option allows you to specify a function that will be called before a route is loaded. It recieves all of the same arguments that the `route.loader` function does. This is a great place to check if a user is authenticated, and redirect them to a login page if they are not.

The `beforeLoad` function runs in relative order to these other route loading functions:

- Route Matching (Top-Down)
- `route.parseParams`
- `route.validateSearchParams`
- Route Loading (Top-Down, including Preloading)
- `route.onParseParamsError`
- `route.onValidateSearchError`
- **`route.beforeLoad`**
- `route.onBeforeLoadError`
- `route.onError`
- Route Loading (Parallel)
- `route.component.preload?`
- `route.loader`

**It's important to know that the `beforeLoad` function for a route is called _before any of it's child routes' `beforeLoad` functions_.** It is essentially a middleware function for the route and all of it's children.

**If you throw an error in `beforeLoad`, none of its children will attempt to load**.

## Redirecting

While not required, some authentication flows require redirecting to a login page. To do this, you can **throw a `redirect()`** from `beforeLoad`:

```tsx
const authenticatedRoute = new Route({
id: 'authenticated',
beforeLoad: async () => {
if (!isAuthenticated()) {
throw redirect({
to: '/login',
search: {
// Use the current location to power a redirect after login
// (Do not use `router.state.resolvedLocation` as it can
// potentially lag behind the actual current location)
redirect: router.state.location.href,
},
})
}
},
})
```

> 🧠 `redirect()` takes all of the same options as the `navigate` function, so you can pass options like `replace: true` if you want to replace the current history entry instead of adding a new one.
Once you have authenticated a user, it's also common practice to redirect them back to the page they were trying to access. To do this, you can utilize the `redirect` search param that we added in our original redirect. Since we'll be replace the entire URL with what it was, `router.history.push` is better suited for this than `router.navigate`:

```tsx
router.history.push(search.redirect)
```

## Non-Redirected Authentication

Some applications choose to not redirect users to a login page, and instead keep the user on the same page and show a login form that either replaces the main content or hides it via a modal. This is also possible with TanStack Router by simply short circuiting rendering the <Outlet /> that would normally render the child routes:

```tsx
const authenticatedRoute = new Route({
id: 'authenticated',
component: () => {
if (!isAuthenticated()) {
return <Login />
}

return <Outlet />
},
})
```

This keeps the user on the same page, but still allows you to render a login form. Once the user is authenticated, you can simply render the <Outlet /> and the child routes will be rendered.
14 changes: 5 additions & 9 deletions packages/router/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@ export interface RouterOptions<
basepath?: string
createRoute?: (opts: { route: AnyRoute; router: AnyRouter }) => void
onRouteChange?: () => void
// fetchServerDataFn?: FetchServerDataFn
context?: TRouteTree['__types']['routerContext']
Wrap?: React.ComponentType<{
children: React.ReactNode
Expand Down Expand Up @@ -745,23 +744,20 @@ export class Router<

try {
await route.options.beforeLoad?.({
router: this as any,
match,
...match,
preload: !!opts?.preload,

Check failure on line 748 in packages/router/src/router.ts

View workflow job for this annotation

GitHub Actions / Test & Publish

Argument of type '{ preload: boolean; id: string; routeId: string; pathname: string; params: any; status: "error" | "pending" | "success"; error: unknown; paramsError: unknown; searchError: unknown; updatedAt: number; loader: any; loadPromise?: Promise<...> | undefined; ... 6 more ...; abortController: AbortController; }' is not assignable to parameter of type '{ router: AnyRouter; match: AnyRouteMatch; }'.
})
} catch (err) {
handleError(err, route.options.onBeforeLoadError)
}
}),
)
} catch (err) {
if (isRedirect(err)) {
if (!opts?.preload) {
this.navigate(err as any)
}
return
if (!opts?.preload) {
this.navigate(err as any)
}

throw err // we should never end up here
throw err
}

const validResolvedMatches = resolvedMatches.slice(0, firstBadMatchIndex)
Expand Down

0 comments on commit 734f642

Please sign in to comment.