Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useRoutePaths #9755

Merged
merged 9 commits into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
```

## Navigation

### navigate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ ${routes.map(
}
).join('\n')}
}

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

//# sourceMappingURL=web-routerRoutes.d.ts.map
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 @@ -19,6 +19,8 @@ export * from './route-announcement'
export { default as RouteFocus } from './route-focus'
export * from './route-focus'

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 @@ -23,6 +23,7 @@ type UseAuth = () => AuthContextInterface<
export interface RouterState {
paramTypes?: Record<string, ParamType>
useAuth: UseAuth
routes: ReturnType<typeof analyzeRoutes>
}

const RouterStateContext = createContext<RouterState | undefined>(undefined)
Expand All @@ -45,6 +46,7 @@ const RouterSetContext = createContext<
export interface RouterContextProviderProps
extends Omit<RouterState, 'useAuth'> {
useAuth?: UseAuth
routes: ReturnType<typeof analyzeRoutes>
children: React.ReactNode
}

Expand All @@ -55,11 +57,13 @@ function stateReducer(state: RouterState, newState: Partial<RouterState>) {
export const RouterContextProvider: React.FC<RouterContextProviderProps> = ({
useAuth,
paramTypes,
routes,
children,
}) => {
const [state, setState] = useReducer(stateReducer, {
useAuth: useAuth || useNoAuth,
paramTypes,
routes,
})

return (
Expand Down
31 changes: 21 additions & 10 deletions packages/router/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ function Route(_props: RouteProps | RedirectRouteProps | NotFoundRouteProps) {
return <></>
}

export interface RouterProps extends RouterContextProviderProps {
export interface RouterProps
extends Omit<RouterContextProviderProps, 'routes'> {
trailingSlashes?: TrailingSlashesTypes
pageLoadingDelay?: number
children: ReactNode
Expand Down Expand Up @@ -91,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 @@ -106,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 @@ -130,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 @@ -165,7 +172,11 @@ const LocationAwareRouter: React.FC<RouterProps> = ({

// Level 2/3 (LocationAwareRouter)
return (
<RouterContextProvider useAuth={useAuth} paramTypes={paramTypes}>
<RouterContextProvider
useAuth={useAuth}
paramTypes={paramTypes}
routes={analyzeRoutesResult}
>
<ParamsProvider allParams={allParams}>
<PageLoadingContextProvider delay={pageLoadingDelay}>
{redirect && <Redirect to={replaceParams(redirect, 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]
}
Loading