Skip to content

Commit

Permalink
Merge branch 'main' into tobbe-use-route-name
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobbe committed Dec 27, 2023
2 parents a0b1120 + f3e1c37 commit 230d10b
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 10 deletions.
41 changes: 41 additions & 0 deletions docs/docs/router.md
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,47 @@ const App = () => {
}
```

## useRoutePaths

`useRoutePaths()` is a React hook you can use to get a map of all routes mapped to their literal paths, as they're defined in your routes file.

Example usage:

```jsx
const routePaths = useRoutePaths()

return <pre><code>{JSON.stringify(routePaths, undefined, 2)}</code></pre>
```

Example output:

```
{
"home": "/"
"about": "/about",
"login": "/login",
"signup": "/signup",
"forgotPassword": "/forgot-password",
"resetPassword": "/reset-password",
"newContact": "/contacts/new",
"editContact": "/contacts/{id:Int}/edit",
"contact": "/contacts/{id:Int}",
"contacts": "/contacts",
}
```

## useRoutePath

This is a convenience hook for when you only want the path for a single route.
```jsx
const aboutPath = useRoutePath('about') // returns "/about"
```
is the same as
```jsx
const routePaths = useRoutePaths()
const aboutPath = routePaths.about // Also returns "/about"
```

## useRouteName

Use the `useRouteName()` hook to get the name of the current route (the page
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ ${routes.map(
).join('\n')}
}

export function useRoutePaths(): Record<keyof AvailableRoutes, string>
export function useRoutePath(routeName: keyof AvailableRoutes): string

/** Gets the name of the current route (as defined in your Routes file) */
export function useRouteName(): keyof AvailableRoutes | undefined
}
Expand Down
51 changes: 51 additions & 0 deletions packages/router/src/__tests__/useRoutePaths.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/** @jest-environment jsdom */
import React from 'react'

import { render } from '@testing-library/react'

import { Route, Router } from '../router'
import { Set } from '../Set'
import { useRoutePaths, useRoutePath } from '../useRoutePaths'

test('useRoutePaths and useRoutePath', async () => {
const HomePage = () => {
const routePaths = useRoutePaths()
// Sorry about the `as never` stuff here. In an actual project we have
// generated types to use, but not here
const homePath = useRoutePath('home' as never)

return (
<>
<h1>Home Page</h1>
<p>My path is {homePath}</p>
<p>All paths: {Object.values(routePaths).join(',')}</p>
</>
)
}

interface LayoutProps {
children: React.ReactNode
}

const Layout = ({ children }: LayoutProps) => <>{children}</>

const Page = () => <h1>Page</h1>

const TestRouter = () => (
<Router>
<Route path="/" page={HomePage} name="home" />
<Set wrap={Layout}>
<Route path="/one" page={Page} name="one" />
</Set>
<Set wrap={Layout}>
<Route path="/two/{id:Int}" page={Page} name="two" />
</Set>
</Router>
)

const screen = render(<TestRouter />)

await screen.findByText('Home Page')
await screen.findByText(/^My path is\s+\/$/)
await screen.findByText(/^All paths:\s+\/,\/one,\/two\/\{id:Int\}$/)
})
2 changes: 2 additions & 0 deletions packages/router/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export { default as RouteFocus } from './route-focus'
export * from './route-focus'
export * from './useRouteName'

export * from './useRoutePaths'

export { parseSearch, getRouteRegexAndParams, matchPath } from './util'

/**
Expand Down
6 changes: 5 additions & 1 deletion packages/router/src/router-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { useReducer, createContext, useContext } from 'react'
import type { AuthContextInterface } from '@redwoodjs/auth'
import { useNoAuth } from '@redwoodjs/auth'

import type { ParamType } from './util'
import type { ParamType, analyzeRoutes } from './util'

type UseAuth = () => AuthContextInterface<
unknown,
Expand All @@ -24,6 +24,7 @@ export interface RouterState {
paramTypes?: Record<string, ParamType>
activeRouteName?: string | undefined | null
useAuth: UseAuth
routes: ReturnType<typeof analyzeRoutes>
}

const RouterStateContext = createContext<RouterState | undefined>(undefined)
Expand All @@ -44,6 +45,7 @@ const RouterSetContext = createContext<
export interface RouterContextProviderProps
extends Omit<RouterState, 'useAuth'> {
useAuth?: UseAuth
routes: ReturnType<typeof analyzeRoutes>
activeRouteName?: string | undefined | null
children: React.ReactNode
}
Expand All @@ -55,13 +57,15 @@ function stateReducer(state: RouterState, newState: Partial<RouterState>) {
export const RouterContextProvider: React.FC<RouterContextProviderProps> = ({
useAuth,
paramTypes,
routes,
activeRouteName,
children,
}) => {
const [state, setState] = useReducer(stateReducer, {
useAuth: useAuth || useNoAuth,
activeRouteName,
paramTypes,
routes,
})

return (
Expand Down
25 changes: 16 additions & 9 deletions packages/router/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function Route(_props: RouteProps | RedirectRouteProps | NotFoundRouteProps) {
}

export interface RouterProps
extends Omit<RouterContextProviderProps, 'activeRouteName'> {
extends Omit<RouterContextProviderProps, 'routes' | 'activeRouteName'> {
trailingSlashes?: TrailingSlashesTypes
pageLoadingDelay?: number
children: ReactNode
Expand Down Expand Up @@ -92,13 +92,7 @@ const LocationAwareRouter: React.FC<RouterProps> = ({
}) => {
const location = useLocation()

const {
pathRouteMap,
hasHomeRoute,
namedRoutesMap,
NotFoundPage,
activeRoutePath,
} = useMemo(() => {
const analyzeRoutesResult = useMemo(() => {
return analyzeRoutes(children, {
currentPathName: location.pathname,
// @TODO We haven't handled this with SSR/Streaming yet.
Expand All @@ -107,6 +101,14 @@ const LocationAwareRouter: React.FC<RouterProps> = ({
})
}, [location.pathname, children, paramTypes])

const {
pathRouteMap,
hasHomeRoute,
namedRoutesMap,
NotFoundPage,
activeRoutePath,
} = analyzeRoutesResult

// Assign namedRoutes so it can be imported like import {routes} from 'rwjs/router'
// Note that the value changes at runtime
Object.assign(namedRoutes, namedRoutesMap)
Expand All @@ -131,7 +133,11 @@ const LocationAwareRouter: React.FC<RouterProps> = ({
if (!activeRoutePath) {
if (NotFoundPage) {
return (
<RouterContextProvider useAuth={useAuth} paramTypes={paramTypes}>
<RouterContextProvider
useAuth={useAuth}
paramTypes={paramTypes}
routes={analyzeRoutesResult}
>
<ParamsProvider>
<PageLoadingContextProvider delay={pageLoadingDelay}>
<ActiveRouteLoader
Expand Down Expand Up @@ -169,6 +175,7 @@ const LocationAwareRouter: React.FC<RouterProps> = ({
<RouterContextProvider
useAuth={useAuth}
paramTypes={paramTypes}
routes={analyzeRoutesResult}
activeRouteName={name}
>
<ParamsProvider allParams={allParams}>
Expand Down
27 changes: 27 additions & 0 deletions packages/router/src/useRoutePaths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useRouterState } from './router-context'
import type { GeneratedRoutesMap } from './util'

import type { AvailableRoutes } from '.'

// This has to be a function, otherwise we're not able to do declaration merging
export function useRoutePaths() {
const routerState = useRouterState()

const routePaths = Object.values(routerState.routes.pathRouteMap).reduce<
Record<keyof GeneratedRoutesMap, string>
>((routePathsAcc, currRoute) => {
if (currRoute.name) {
routePathsAcc[currRoute.name] = currRoute.path
}

return routePathsAcc
}, {})

return routePaths
}

export function useRoutePath(routeName: keyof AvailableRoutes) {
const routePaths = useRoutePaths()

return routePaths[routeName]
}

0 comments on commit 230d10b

Please sign in to comment.