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

usePathMatch #9774

Closed
wants to merge 3 commits into from
Closed
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
113 changes: 113 additions & 0 deletions packages/router/src/__tests__/usePathMatch.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React from 'react'

import * as testingLibraryReact from '@testing-library/react'

import { LocationProvider } from '..'
import { usePathMatch } from '../usePathMatch'

function createDummyLocation(pathname: string, search = '') {
return {
pathname,
hash: '',
host: '',
hostname: '',
href: '',
ancestorOrigins: null,
assign: () => null,
reload: () => null,
replace: () => null,
origin: '',
port: '',
protocol: '',
search,
}
}

describe('usePathMatch', () => {
let locationPathname = '/dummy-location'
let locationSearch = ''

type CallbackType = Parameters<typeof testingLibraryReact.renderHook>[0]
function renderHook(cb: CallbackType) {
const mockLocation = createDummyLocation(locationPathname, locationSearch)

return testingLibraryReact.renderHook(cb, {
wrapper: ({ children }) => (
<LocationProvider location={mockLocation}>{children}</LocationProvider>
),
})
}

function setLocation(pathname: string, search = '') {
locationPathname = pathname
locationSearch = search
}

afterEach(() => {
setLocation('/dummy-location')
})

it('matches on identical url and path', () => {
const { result } = renderHook(() => usePathMatch('/about', '/about'))

expect(result.current).toBeTruthy()
})

it('matches on location', () => {
setLocation('/test-path')

const { result } = renderHook(() => usePathMatch('/test-path'))

expect(result.current).toBeTruthy()
})

it('matches a path with given param value', () => {
const { result } = renderHook(() =>
usePathMatch('/posts/uuid-string', '/posts/{id}', {
paramValues: { id: 'uuid-string' },
})
)

expect(result.current).toBeTruthy()
})

it('matches a path with given param value, using location', () => {
setLocation('/posts/uuid-string')

const { result } = renderHook(() =>
usePathMatch('/posts/{id}', { paramValues: { id: 'uuid-string' } })
)

expect(result.current).toBeTruthy()
})

it('matches a path with default param type', () => {
const { result } = renderHook(() =>
usePathMatch('/posts/123', '/posts/{id}', {
paramValues: { id: '123' },
})
)

expect(result.current).toBeTruthy()
})

it('matches a path with a specified param type', () => {
const { result } = renderHook(() =>
usePathMatch('/posts/123', '/posts/{id:Int}', {
paramValues: { id: 123 },
})
)

expect(result.current).toBeTruthy()
})

it("doesn't match a path with a specified param type with different value", () => {
const { result } = renderHook(() =>
usePathMatch('/posts/123', '/posts/{id:Int}', {
paramValues: { id: '0' },
})
)

expect(result.current).toBeFalsy()
})
})
10 changes: 9 additions & 1 deletion packages/router/src/useMatch.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { LocationContextType } from './location'
import { useLocation } from './location'
import { matchPath } from './util'
import type { FlattenSearchParams } from './util'
Expand Down Expand Up @@ -32,7 +33,14 @@ type UseMatchOptions = {
* const match = useMatch('/product', { matchSubPaths: true })
*/
export const useMatch = (pathname: string, options?: UseMatchOptions) => {
const location = useLocation()
return useLocationMatch(useLocation(), pathname, options)
}

export const useLocationMatch = (
location: LocationContextType,
pathname: string,
options?: UseMatchOptions
) => {
if (!location) {
return { match: false }
}
Expand Down
69 changes: 69 additions & 0 deletions packages/router/src/usePathMatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { useLocation } from './location'
import { useLocationMatch } from './useMatch'

interface UsePathMatchOptions {
paramValues?: Record<string, any>
Copy link
Member Author

@Tobbe Tobbe Jan 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

routeParams instead

}

/**
* Will match the current location against the given path, also matching all
* supplied param values
*
* @param path The path as defined in your Routes file
* @param options {UsePathMatchOptions}
* @param options.paramValues An map of param names and their values
*/
export function usePathMatch(
path: string,
options?: UsePathMatchOptions
): boolean
/**
* Will match the given url against the given path, also matching all supplied
* param values
*
* @param url The url to match against
* @param path The path as defined in your Routes file
* @param options {UsePathMatchOptions}
* @param options.paramValues An map of param names and their values
*/
export function usePathMatch(
url: string,
path: string,
options?: UsePathMatchOptions
): boolean
export function usePathMatch(
urlOrPath: string,
pathOrOptions?: string | UsePathMatchOptions,
opts?: UsePathMatchOptions
): boolean {
const location = useLocation()

const url = typeof pathOrOptions !== 'string' ? location.pathname : urlOrPath
const path = typeof pathOrOptions === 'string' ? pathOrOptions : urlOrPath
const options = typeof pathOrOptions !== 'string' ? pathOrOptions : opts

const match = useLocationMatch({ pathname: url }, path)

if (!match.match) {
return false
}

const paramValues = Object.entries(options?.paramValues || {})

if (paramValues.length > 0) {
if (!isMatchWithParams(match) || !match.params) {
return false
}

// If paramValues were given, they must all match
return paramValues.every(([key, value]) => {
return match.params[key] === value
})
}

return true
}

function isMatchWithParams(match: unknown): match is { params: any } {
return match !== null && typeof match === 'object' && 'params' in match
}
Loading