fenok / react-router-typesafe-routes Goto Github PK
View Code? Open in Web Editor NEWComprehensive and extensible type-safe routes for React Router v6 with first-class support for nested routes and param validation.
License: MIT License
Comprehensive and extensible type-safe routes for React Router v6 with first-class support for nested routes and param validation.
License: MIT License
Hello! Thank you for a very useful library, I enjoy using it in my React projects π
I am stuck with implementing a specific scenario. The idea is that route
prop should only accept routes with id
param in it.
I pass a proper type to Route.TPathTypes
, so that I get TS error when trying to pass a route that does not have id
param.
But I'm not sure how to figure out a constraint for Route.TPath
in the same way.
const idParams = types({ params: { id: string() } }).params;
type IdRoute = Route<string, typeof idParams, any, any, any>;
type Props = {
route?: IdRoute;
}
const Component = ({ route }: Props) => {
const path = route.buildPath({})
}
This gives an error as expected.
But here, I expect typescript to give me an error as well since I don't pass an id
here.
Hey, great library!
I saw the in the docs that mention the following:
βChild routes have to start with an uppercase letter to prevent overlapping with route API.
I was wondering if you could go more in detail of what this means, as I like my routes in lowercase haha.
Thanks!
Playing with the v6 branch.
It'd be nice if you could something like
const detailsRoutes = PRODUCT.DETAILS.uninline(PRODUCT).
So you could then use inline children to get the easy dot notation access, but break it apart when creating routes.
Hi! I've just found this wonderful looking project and would love to use it in one of my project. However, when I bundle my project I get these errors. Any ideas?
β [ERROR] No matching export in "node_modules/react-router-dom/esm/react-router-dom.js" for import "useSearchParams"
node_modules/react-router-typesafe-routes/dom/useTypedSearchParams.js:12:9:
12 β import { useSearchParams, createSearchParams } from "react-router-dom";
β΅ ~~~~~~~~~~~~~~~
β [ERROR] No matching export in "node_modules/react-router-dom/esm/react-router-dom.js" for import "createSearchParams"
node_modules/react-router-typesafe-routes/dom/useTypedSearchParams.js:12:26:
12 β import { useSearchParams, createSearchParams } from "react-router-dom";
β΅ ~~~~~~~~~~~~~~~~~~
β [ERROR] No matching export in "node_modules/react-router-dom/esm/react-router-dom.js" for import "createSearchParams"
node_modules/react-router-typesafe-routes/dom/route.js:1:9:
1 β import { createSearchParams, generatePath } from "react-router-dom";
β΅ ~~~~~~~~~~~~~~~~~~
Please let me know if there is any more information I can provide to help debug this.
Hi, when I try to use the path value '/'
for a route I get the error:
TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
This happens only for this value. Feels like there's an explicit check for this value in particular, but I wonder why since I have been always able to use it without issues with react-router?
export const ROUTES = {
DEFAULT: route('/'), // <- TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
};
<Routes>
<Route
path={'/'} // <- not a problem
element={<MyComponent />}
/>
const routes = {
root: route('', {}, { auth: route('auth', {}) }),
};
Throws
Argument of type '{ auth: RouteWithChildren<void, "auth", Record<never, never>, Record<never, never>, never[], Record<never, never>>; }' is not assignable to parameter of type 'undefined'.
I am using typescript 4.9.4.
Hi @fenok,
Thanks for your work on this library!
I found that when using route.buildPath
the params are not encoded, resulting in invalid links when slashes are used. Is this intentional? I created a Sandbox to demonstrate the issue.
https://codesandbox.io/p/sandbox/falling-firefly-n4kvsg
Hello,
Thanks for your work on this, I believe it can really help my current project. My organization uses sub-routers to add shared wrappers/organize code specific to a particular kind of entity.
For example:
<Routes>
<Route path={ROUTES.HOME.path} element={<HomePage />} />
<Route path={ROUTES.USERS.path} element={<UserRouter />} />
</Routes>
UserRouter
would render the following:
<Routes>
<Route path={ROUTES.USERS.$.LIST.path} element={<UserListPage />} />
<Route path={ROUTES.USERS.$.DETAILS.path} element={<UserDetailsPage />} />
</Routes>
ROUTES
would look something like this:
export const ROUTES = {
HOME: route(''),
USERS: route(
'users/*',
{},
{
LIST: route(''),
DETAILS: route(':id'),
}
),
};
How would one navigate to ROUTES.USERS.LIST.path
while excluding the indeterminate star introduced by USERS
route
? Does this project support that? relativePath
omits it, as noted in the README, but I'm curious if this use case is supported.
console.log(ROUTES.USERS.LIST.path); // prints `users/*`, I'm looking to just print `users/`, potentially at depth (nested routers within nested routers)
If I have a ROUTES like this:
const TEST_ROUTES = {
EXAMPLE: route(
'example',
{ searchParams: { q: string() } },
{ DETAILS: route(':id') }
),
};
I would expect to want to navigate to example?q=My+query
but not example/1?q=My+query
. The typescript is letting this go through. If your application is built correctly, this is going to end up rendering fine but will have an awkward URL (and is a sign that your link is being constructed wrong).
Eg: this does not match the @ts-expect-error I want --
// @ts-expect-error - extra search parameters here.
TEST_ROUTES.EXAMPLE.DETAILS.buildPath({ id: '1' }, { q: 'my query' })
Is this a bug or expected behavior? If it's expected, can you help me understand the scenario it's supporting?
Current names are misleading because these functions don't generate full URLs. Instead, they generate URL parts which are called paths in React Router. There already are buildPath
and buildRelativePath
functions, but they actually generate pathnames, so they can also be renamed.
These are the possible renamings:
buildUrl
=> buildPath
buildRelativeUrl
=> buildRelativePath
buildPath
=> buildPathname
buildRelativePath
=> buildRelativePathname
It won't even be a breaking change, because buildUrl
can be safely used instead of buildPath
.
First off, thanks for creating this library, I love the concept. It's very possible I'm doing something wrong here.
Your readme says "React Router allows absolute child route paths if they match the parent path." I have a pretty simple structure, and I was able to get it working with relative paths, but not absolute.
I have a parent route /regions/:regionSlug
, and the then a child: /regions/:regionSlug/clusters/:id/:clusterName
. The child never seems to match.
Here is the typed definition:
const routes = {
Region: route(
"regions/:regionSlug",
{},
{
Clusters: route(
"clusters",
{},
{
List: route(""),
Details: route(":id/:clusterName"),
}
),
}
),
};
Here is are the actual routes:
<Route path={routes.Region.path} element={<Region />}>
<Route
path={routes.Region.Clusters.Details.path}
element={<ClusterDetails />}
/>
</Route>
The region route matches, but the nested route doesn't.
π
Firstly thank you for the amazing library! I am trying to replicate the ArrayParam
behaviour from use-query-params.
?qp=one&qp=two
If qp
is missing I want to set the default values to ['one', 'two']
. However it looks like default()
only expects a string (unless I am missing something?).
I'm using react-router-typesafe-routes in a TypeScript project that compiles with Vite. Vite has no issues with the ESM-only output of this package, but I am unable to run my Playwright tests because they require CommonJS imports.
Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/user/Desktop/path/to/project/node_modules/react-router-typesafe-routes/dom/index.js from /Users/user/Desktop/ruter/path/to/project/src/routes.ts not supported.
Instead change the require of index.js in /Users/jonasjensen/Desktop/ruter/bestillingstransport/apps/bt-frontend/src/admin/routing/routes.ts to a dynamic import() which is available in all CommonJS modules.
at ../src/admin/routing/routes.ts:7
5 | OVERVIEW: route('overview'),
6 | PROFILE: route('profile'),
> 7 | TRIPS: route('trips', {}, {LIVE: route('live')}),
| ^
8 | MORE_ROUTES: route('more_routes', {}, {CLIENTS: route('clients')}),
I unfortunately discovered this only after I'd already rewrite all hardcoded paths in my entire codebase to use react-router-typesafe-routes
. π¬ It would be very helpful if the package could support both ESM and CommonJS formats.
It would be great if react-router-typesafe-routes
could provide a CommonJS output alongside the ESM output. This would make the package compatible with various testing environments and setups, including those that rely on CommonJS.
(As a workaround, I've tried bundling and transforming my tests using esbuild, but this adds too much extra complexity and I couldn't even get it to work properly..)
Thank you for considering this feature request, and I appreciate your efforts in creating and maintaining this package! I compared all the different "typesafe routes" packages that I could find and this one was by far the best I could find. π
We started playing with this package for v6 (which is looking great π ) when it was using parsers like numberParser
these threw if it was bad input.
Now with the move to numberType
you either pass a fallback or the returned value could be undefined which requires checking for bad input in every route or (just swallowing it by passing -1
or NaN
)
Whilst the errors were jarring at first, using an ErrorBoundary
you could handle them globally or per nested set of routes, especially if the error thrown was rich.
Curious on what you are thinking for handling parse errors going forward (just rely on fallbacks, is there an option to still throw)?
There are cases when routes can share the same set of parameters (most likely search parameters and state fields). One common example is pagination params.
We likely want a common helper for these parameters as well, so we need an actual route for them.
We could use inheritance, but it can quickly get ugly: PAGINATION.FILTER.ORDER.ACTUAL_ROUTE.buildUrl(...)
.
We can use route types
, but it's relatively verbose:
const PAGINATION_FRAGMENT = route( '', { searchParams: { offset: numberType } } );
const LIST = route( 'list', { searchParams: { ...PAGINATION_FRAGMENT.types.searchParams, customParam: stringType } } )
It would be nice to have an API for route composition, something like this:
const LIST = route( 'list', {searchParams: { customParam: stringType }}, {}, [PAGINATION_FRAGMENT] );
However, I can't figure out how to write types for this (when there are multiple fragments).
As of now, if we want to implement a common helper which only handles a portion of search params, but leaves other search params intact, we have to resort to using URLSearchParams
object.
Thoughts:
setTypedSearchParams
(of useTypedSearchParams
), like preserveNonTyped
. If true
, the given object is merged with URLSearchParams object (containing only non-typed params).Questions:
build*
APIs?restUrlSearchParams: URLSearchParams
from useTypedSearchParams
?<Route>
and matchPath
in case of optional params remix-run/react-router#9862, remix-run/react-router#9933<Routes />
remix-run/react-router#9929My current setup is a monorepo where the URLs are shared between frontend app (using react-router) and a backend app (using node), my concern is that since this relies on react-router
it will not work on the backend app, or am I missing something?
I have an application with heavy use of searchParams
and I'm looking for a way to make it harder for developers to invoke buildPath
incorrectly.
In the example below, you can see that it's very easy to call buildPath
with the searchParameters as the first argument.
I think this is because the first argument to buildPath in this situation is Record<never, never>
instead of Record<string, never>
.
Any tips on either:
(1) where to fix this in the library
(2) how to build the ROUTES differently to get type safety without any upstream fix
(3) a pointer on if I'm doing something completely wrong.
Thanks!
import { route, string } from 'react-router-typesafe-routes/dom';
const ROUTES = {
WIDGETS: route('widgets', { searchParams: { order: string() } }),
};
// This is what I want to invoke:
ROUTES.WIDGETS.buildPath({}, { order: 'asc' }); // '/widgets?order=asc'
// This silently fails -- it passes the typescript tests
ROUTES.WIDGETS.buildPath({ order: 'asc' }); // '/widgets'
As of now, parsing and validation are both done via getTyped
method. This leads to the following drawbacks:
useTypedSearchParams
could be slightly improved. We can specify initial search params, which behave similarly to fallbacks. If we knew which types don't include validation, we could type the corresponding returned params as non-undefined
regardless of the fallbacks of their types. Or we could change useTypedSearchParams
to accept actual fallbacks and just validate them.We could split getTyped
into required getTyped
and optional validate
, but it might be inconvenient for creating custom types.
Overall, it's not clear which approach is best.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.