feat: enable dynamic routes on frontend

This commit is contained in:
Valerio Ageno
2024-06-02 12:14:56 +02:00
parent 64c37f42c5
commit 7557df10f3
5 changed files with 95 additions and 7 deletions
-1
View File
@@ -1 +0,0 @@
README.md
+7 -3
View File
@@ -8,7 +8,7 @@
"lint": "eslint --ext .ts,.tsx ./src -c ../../.eslintrc",
"format": "prettier -u --write '**/*'",
"types": "tsc --noEmit",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "vitest"
},
"type": "module",
"types": "dist/esm/index.d.ts",
@@ -86,12 +86,16 @@
"@types/node": "^20.12.7",
"@vitejs/plugin-react-swc": "^3.7.0",
"fast-text-encoding": "^1.0.6",
"prettier": "^3.2.4",
"prettier": "^3.2.4",
"vite": "^5.2.11",
"zustand": "4.4.7"
},
"devDependencies": {
"@types/babel-traverse": "^6.25.10"
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.7",
"@types/babel-traverse": "^6.25.10",
"jsdom": "^24.0.0",
"vitest": "^1.5.2"
},
"sideEffects": false,
"keywords": [],
@@ -0,0 +1,38 @@
import { afterEach, describe, expect, test, vi } from 'vitest'
import { getRouteByPathname } from './Matches'
import { cleanup } from '@testing-library/react'
describe('Test getRouteByPathname fn', () => {
afterEach(() => {
cleanup()
})
test('match routes by ids', () => {
vi.mock('../hooks/useRouter.tsx', () => ({
useRouter: (): { routesById: Record<string, any> } => {
return {
routesById: {
'/': { id: '/' },
'/about': { id: '/about' },
'/posts/': { id: '/posts/' }, // posts/index
'/posts/[post]': { id: '/posts/[post]' },
'/posts/defined-post': { id: '/posts/defined-post' },
'/posts/[post]/[comment]': { id: '/posts/[post]/[comment]' },
},
}
},
}))
expect(getRouteByPathname('/')?.id).toBe('/')
expect(getRouteByPathname('/not-found')?.id).toBe(undefined)
expect(getRouteByPathname('/about')?.id).toBe('/about')
expect(getRouteByPathname('/posts/')?.id).toBe('/posts/')
expect(getRouteByPathname('/posts/dynamic-post')?.id).toBe('/posts/[post]')
expect(getRouteByPathname('/posts/defined-post')?.id).toBe(
'/posts/defined-post',
)
expect(getRouteByPathname('/posts/dynamic-post/dynamic-comment')?.id).toBe(
'/posts/[post]/[comment]',
)
})
})
@@ -1,5 +1,6 @@
import { useRouter } from '../hooks/useRouter'
import { useRouterStore } from '../hooks/useRouterStore'
import type { Route } from '../route'
import { RouteMatch } from './RouteMatch'
import NotFound from './NotFound'
@@ -8,11 +9,54 @@ interface MatchesProps {
serverSideProps: any
}
const DYNAMIC_PATH_REGEX = /\[(.*?)\]/
export function getRouteByPathname(pathname: string): Route | undefined {
const { routesById } = useRouter()
if (routesById[pathname]) return routesById[pathname]
const dynamicRoutes = Object.keys(routesById).filter((route) =>
DYNAMIC_PATH_REGEX.test(route),
)
if (!dynamicRoutes.length) return
const pathSegments = pathname.split('/').filter(Boolean)
let match = undefined
// TODO: Check algo efficiency
for (const dynamicRoute of dynamicRoutes) {
const dynamicRouteSegments = dynamicRoute.split('/').filter(Boolean)
const routeSegmentsCollector: string[] = []
for (let i = 0; i < dynamicRouteSegments.length; i++) {
if (
dynamicRouteSegments[i] === pathSegments[i] ||
DYNAMIC_PATH_REGEX.test(dynamicRouteSegments[i] || '')
) {
routeSegmentsCollector.push(dynamicRouteSegments[i] ?? '')
} else {
break
}
}
if (routeSegmentsCollector.length === pathSegments.length) {
match = `/${routeSegmentsCollector.join('/')}`
break
}
}
if (!match) return
return routesById[match]
}
export function Matches({ serverSideProps }: MatchesProps): JSX.Element {
const location = useRouterStore((st) => st.location)
const router = useRouter()
const route = router.routesById[location.pathname]
const route = getRouteByPathname(location.pathname)
if (!route) {
return <NotFound />
+4 -1
View File
@@ -1,3 +1,5 @@
/// <reference types="vitest" />
/// <reference types="vite/client" />
import { defineConfig, mergeConfig } from 'vitest/config'
import { tanstackBuildConfig } from '@tanstack/config/build'
import react from '@vitejs/plugin-react'
@@ -6,8 +8,9 @@ const config = defineConfig({
plugins: [react()],
test: {
name: 'react-router',
watch: false,
watch: true,
environment: 'jsdom',
globals: true,
},
})