{"id":28300728,"url":"https://github.com/fenok/react-router-typesafe-routes","last_synced_at":"2025-06-17T06:31:45.682Z","repository":{"id":39915651,"uuid":"378998595","full_name":"fenok/react-router-typesafe-routes","owner":"fenok","description":"Enhanced type safety via validation for all route params in React Router v7.","archived":false,"fork":false,"pushed_at":"2024-11-24T12:46:49.000Z","size":143443,"stargazers_count":159,"open_issues_count":4,"forks_count":3,"subscribers_count":4,"default_branch":"dev","last_synced_at":"2025-06-14T04:44:28.134Z","etag":null,"topics":["react","react-router","type-safe","typescript","validation"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fenok.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-06-21T16:40:48.000Z","updated_at":"2025-06-08T06:12:33.000Z","dependencies_parsed_at":"2024-04-21T20:47:58.032Z","dependency_job_id":"edeb41ed-ba90-4ade-a626-cd6586650f2a","html_url":"https://github.com/fenok/react-router-typesafe-routes","commit_stats":{"total_commits":195,"total_committers":1,"mean_commits":195.0,"dds":0.0,"last_synced_commit":"3eaa6b8e443ac6a7f96b820b6d6b06b4dc47ae24"},"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"purl":"pkg:github/fenok/react-router-typesafe-routes","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fenok%2Freact-router-typesafe-routes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fenok%2Freact-router-typesafe-routes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fenok%2Freact-router-typesafe-routes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fenok%2Freact-router-typesafe-routes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fenok","download_url":"https://codeload.github.com/fenok/react-router-typesafe-routes/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fenok%2Freact-router-typesafe-routes/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260306800,"owners_count":22989664,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["react","react-router","type-safe","typescript","validation"],"created_at":"2025-05-23T19:11:41.797Z","updated_at":"2025-06-17T06:31:45.660Z","avatar_url":"https://github.com/fenok.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React Router Typesafe Routes 🍣\n\nEnhanced type safety via validation for all route params in React Router v7.\n\n[![NPM Version](https://img.shields.io/npm/v/react-router-typesafe-routes)](https://www.npmjs.com/package/react-router-typesafe-routes)\n[![NPM Downloads](https://img.shields.io/npm/dw/react-router-typesafe-routes)](https://www.npmjs.com/package/react-router-typesafe-routes)\n\nThe library provides type safety for all route params (pathname params, search params (including multiple keys), state, and hash) on building and parsing/validating URL parts and state. There are no unsafe type casts whatsoever.\n\nIf you want, you can use a validation library. There is first-party support for [Zod](https://github.com/colinhacks/zod) and [Yup](https://github.com/jquense/yup), and other libraries can be integrated with ease. Otherwise, you can use other built-in types and fine-tune their validation instead.\n\nIn built-in types, parsing and validation errors are caught and replaced with `undefined`. You can also return a default value or throw an error in case of an absent or invalid param. All these adjustments reflect in types, too!\n\nBuilt-in types allow to customize stringification and parsing as well. If you need more control, you can build completely custom types, which means that params serialization and deserialization are fully customizable.\n\nThe library doesn't restrict or alter React Router API in any way, including nested routes and relative links. It can also be gradually adopted.\n\n## Installation\n\n```\nyarn add react-router-typesafe-routes\n```\n\nNote that `react-router` and `react` are peer dependencies.\n\nThere are optional entry points for types based on third-party validation libraries:\n\n- `react-router-typesafe-routes/zod` exports `zod` type, `zod` is a peer dependency;\n- `react-router-typesafe-routes/yup` exports `yup` type, `yup` is a peer dependency;\n\nThe library is targeting ES6 (ES2015).\n\nThe minimal required version of TypeScript is `5.0`, and `strict` mode must be enabled.\n\n## Limitations \u0026 Caveats\n\n- React Router only considers pathnames during route matching, so search params, hash, and state are always optional upon URL or state building.\n- For convenience, absent and invalid params are considered virtually the same by built-in types. However, you retain full control with custom types.\n- To emphasize that route relativity is governed by the library, leading slashes in path patterns are forbidden. Trailing slashes are also forbidden due to being purely cosmetic.\n\n## How is it different from existing solutions?\n\n| Feature                                           | react-router-typesafe-routes | [typesafe-routes](https://github.com/kruschid/typesafe-routes) | [typed-react-router](https://github.com/bram209/typed-react-router) | [typesafe-router](https://github.com/jamesopstad/typesafe-router) |\n| ------------------------------------------------- | ---------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------- |\n| Type-safe pathname params                         | ✅                           | ✅                                                             | ✅                                                                  | ✅                                                                |\n| Type-safe search params                           | ✅                           | ✅                                                             | 🚫                                                                  | 🚫                                                                |\n| Multiple identical keys in search params          | ✅                           | 🚫️                                                            | 🚫                                                                  | ✅                                                                |\n| Type-safe hash                                    | ✅                           | 🚫                                                             | 🚫                                                                  | 🚫                                                                |\n| Type-safe state                                   | ✅                           | 🚫                                                             | 🚫                                                                  | 🚫                                                                |\n| Customizable serialization / parsing / validation | ✅                           | ✅                                                             | 🚫                                                                  | 🚫                                                                |\n| Customizable built-in types                       | ✅                           | 🚫                                                             | 🚫                                                                  | 🚫                                                                |\n| Nested routes                                     | ✅                           | ✅                                                             | ✅                                                                  | ✅                                                                |\n| Relative links                                    | ✅                           | ✅                                                             | 🚫                                                                  | ✅                                                                |\n| Type-safe actions/loaders                         | 🚫                           | 🚫                                                             | 🚫                                                                  | ✅                                                                |\n\n\u003e Type-safe actions/loaders should be fairly easy to implement, but it's a low-priority task. Please open an issue if you need them.\n\nOther libraries that I was able to find are outdated and not really suitable for React Router v7:\n\n- [typesafe-react-router](https://github.com/AveroLLC/typesafe-react-router)\n- [react-typesafe-routes](https://github.com/innFactory/react-typesafe-routes)\n\nYou might also want to use some other router with built-in type safety:\n\n- [TanStack Router](https://github.com/tanstack/router)\n- [Type Route](https://github.com/zilch/type-route)\n- [Chicane](https://github.com/swan-io/chicane)\n\n## Quick usage example\n\nDefine library routes:\n\n```tsx\nimport { route, string, number, boolean, union } from \"react-router-typesafe-routes\";\n\n// Start with a pathless route to specify global params.\nconst root = route({\n  // This global search param has a default value that is used as a fallback upon parsing.\n  searchParams: { utm_campaign: string().default(\"default_campaign\") },\n  // Child routes inherit all parent params.\n  children: {\n    user: route({\n      // Pathname params are inferred and can be overridden partially or completely.\n      // Required params implicitly use 'string().defined()' that can throw upon parsing.\n      path: \"user/:userId\",\n      // Without modifiers, 'undefined' can be returned upon parsing.\n      params: { userId: number() },\n      // Specify hash. To allow any hash, define it as 'string()'.\n      hash: union([\"info\", \"comments\"]),\n      // Specify state parts.\n      state: { fromUserList: boolean() },\n      // Child routes inherit all parent params.\n      children: {\n        // Optional pathname params implicitly use 'string()'.\n        post: route({ path: \"post/:postId?\" }),\n      },\n    }),\n  },\n});\n```\n\nDefine React Router routes:\n\n\u003e [!NOTE]  \n\u003e We will use the JSX style of defining routes throughout the documentation, but you can easily define them in any way that React Router enables. See [Framework Routing](https://reactrouter.com/start/framework/routing) and [Library Routing](https://reactrouter.com/start/library/routing) for more information. Note that at the time of writing there are [some issues](https://github.com/remix-run/react-router/issues/12359) with types generation when absolute paths are used.\n\n```tsx\nimport { Route, Routes } from \"react-router\";\nimport { root } from \"./path/to/routes\";\n\n// Absolute paths\n\u003cRoutes\u003e\n  {/* /user/:userId */}\n  \u003cRoute path={root.user.$path()} element={\u003cUser /\u003e}\u003e\n    {/* /user/:userId/post/:postId? */}\n    \u003cRoute path={root.user.post.$path()} element={\u003cPost /\u003e} /\u003e\n  \u003c/Route\u003e\n\u003c/Routes\u003e;\n\n// Relative paths\n\u003cRoutes\u003e\n  {/* user/:userId */}\n  \u003cRoute path={root.user.$path({ relative: true })} element={\u003cUser /\u003e}\u003e\n    {/* post/:postId? */}\n    {/* $ effectively defines path pattern start. */}\n    \u003cRoute path={root.user.$.post.$path({ relative: true })} element={\u003cPost /\u003e} /\u003e\n  \u003c/Route\u003e\n\u003c/Routes\u003e;\n```\n\nUse `Link` components as usual:\n\n```tsx\nimport { Link } from \"react-router\";\nimport { root } from \"./path/to/routes\";\n\n// Absolute link\n\u003cLink\n  // Everything is optional except for required pathname param (params.userId).\n  to={root.user.post.$buildPath({\n    params: { userId: 1, postId: \"abc\" },\n    searchParams: { utm_campaign: \"campaign\" },\n    hash: \"comments\",\n  })}\n  state={root.user.post.$buildState({ fromUserList: true })}\n\u003e\n  /user/1/post/abc?utm_campaign=campaign#comments\n\u003c/Link\u003e;\n\n// Relative link\n\u003cLink\n  // Everything is optional, because there are no required pathname params.\n  // $ effectively defines path pattern start.\n  to={root.user.$.post.$buildPath({\n    relative: true,\n    params: { postId: \"abc\" },\n    searchParams: { utm_campaign: \"campaign\" },\n    hash: \"info\",\n  })}\n  state={root.user.post.$buildState({ fromUserList: false })}\n\u003e\n  post/abc?utm_campaign=campaign#info\n\u003c/Link\u003e;\n```\n\nGet typed params:\n\n```tsx\nimport {\n  useTypedParams,\n  useTypedSearchParams,\n  useTypedHash,\n  useTypedState,\n} from \"react-router-typesafe-routes\";\nimport { root } from \"./path/to/routes\";\n\n// { userId?: number; postId?: string; }\n// Uses root.user.post.$deserializeParams internally.\nconst { userId, postId } = useTypedParams(root.user.post);\n\n// { utm_campaign: string }.\n// Uses root.user.post.$deserializeSearchParams internally.\nconst [{ utm_campaign }, setTypedSearchParams] = useTypedSearchParams(root.user.post);\n\n// \"info\" | \"comments\" | undefined.\n// Uses root.user.post.$deserializeHash internally.\nconst hash = useTypedHash(root.user.post);\n\n// { fromUserList?: boolean }.\n// Uses root.user.post.$deserializeState internally.\nconst { fromUserList } = useTypedState(root.user.post);\n```\n\n## Advanced examples\n\n### Preserve unrelated search params\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to expand\u003c/summary\u003e\n\n```tsx\nimport { route, string, number, useTypedSearchParams } from \"react-router-typesafe-routes\";\nimport { useSearchParams } from \"react-router\";\n\n// Pathless route is used for simplicity, this works with any route\nexport const searchParamsFragment = route({\n  searchParams: {\n    search: string(),\n    page: number(),\n  },\n});\n\n// In a component body\nconst [typedSearchParams, setTypedSearchParams] = useTypedSearchParams(searchParamsFragment);\nconst rawSearchParams = useSearchParams();\n\n// In JSX\n\u003cLink\n  to={searchParamsFragment.$buildSearch({\n    searchParams: {\n      // Destructure existing params to preserve params typed by the route\n      ...typedSearchParams,\n      page: 1,\n    },\n    // Pass raw search params to preserve params NOT typed by the route\n    untypedSearchParams: rawSearchParams,\n  })}\n\u003e\n  Click\n\u003c/Link\u003e;\n\n// In an event handler\nsetTypedSearchParams(\n  (prevParams) =\u003e ({\n    // Destructure existing params to preserve params typed by the route\n    ...prevParams,\n    page: 1,\n  }),\n  {\n    // Set this flag to preserve params NOT typed by the route\n    untypedSearchParams: true,\n  },\n);\n```\n\n\u003c/details\u003e\n\n### Define arrays\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\n```tsx\nimport { route, number } from \"react-router-typesafe-routes\";\n\nconst myRoute = route({\n  searchParams: {\n    // Every built-in type can be made an array. Arrays can only be used in search and state.\n    // Upon parsing, 'undefined' values are omitted. Absent/invalid array is normalized to [].\n    selectedIds: number().array(),\n    // In niche cases you might want to use '.default()' or '.defined()' for items.\n    // '.defined()' means that an invalid item makes the whole array invalid.\n    selectedItems: number().default(-1).array(),\n  },\n});\n```\n\n\u003c/details\u003e\n\n### Share types between routes\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\n```tsx\nimport { route, useTypedParams, useTypedSearchParams } from \"react-router-typesafe-routes\";\n\n// Pathless routes can be used for type sharing.\nconst fragments = {\n  id: route({\n    params: { id: number() },\n  }),\n  query: route({\n    searchParams: { query: string() },\n  }),\n};\n\n// Pathless routes can also be used anywhere in the route tree.\nconst root = route({\n  // You can specify types directly.\n  searchParams: { utm_campaign: string() },\n  // Or reuse existing pathless routes.\n  compose: [fragments.id],\n  children: {\n    user: route({ path: \"user/:id\", compose: [fragments.query] }),\n    post: route({ path: \"post/:id\" }),\n    // Pathname types are ignored if there are no corresponding params in the pattern.\n    about: route({ path: \"about\" }),\n  },\n});\n\n// You can then build helpers that are reusable between routes:\nconst { id } = useTypedParams(fragments.id);\nconst [{ query }] = useTypedSearchParams(fragments.query);\nconst [{ utm_campaign }] = useTypedSearchParams(root);\n```\n\n\u003c/details\u003e\n\n### Extend hash values\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\n```tsx\nimport { route, string } from \"react-router-typesafe-routes\";\n\n// Hash can only be extended by child routes if it's specified as an array of strings.\nconst myRoute = route({\n  path: \"user\",\n  // This route hash is just \"info\"\n  hash: [\"info\"],\n  children: {\n    details: route({\n      path: \"details\",\n      // This route hash is \"info\" | \"address\"\n      hash: [\"address\"],\n      children: {\n        misc: route({\n          path: \"misc\",\n          // A type overwrites parent hash completely, and subsequent arrays are ignored.\n          hash: string(),\n        }),\n      },\n    }),\n  },\n});\n```\n\n\u003c/details\u003e\n\n### Type non-object states\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\n```tsx\nimport { route, string } from \"react-router-typesafe-routes\";\n\n// A type overwrites parent state completely, and subsequest state objects are ignored.\nconst myRoute = route({\n  path: \"user\",\n  state: string(),\n});\n```\n\n\u003e ❗If you're building from scratch, it's almost certainly a bad idea to use this API. However, it might come in handy if you're typing an existing system where it's not easy to get rid of non-object states.\n\n\u003c/details\u003e\n\n### Add custom validation\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\n```tsx\nimport { route, string, number } from \"react-router-typesafe-routes\";\n\n// You don't need to check that this value is a number.\nconst integer = (value: number) =\u003e {\n  if (!Number.isInteger(value)) {\n    throw new Error(`Expected ${value} to be integer.`);\n  }\n\n  return value;\n};\n\n// You can construct validators via factories.\nconst regExp = (regExp: RegExp) =\u003e (value: string) =\u003e {\n  if (value.match(regExp)?.[0] !== value) {\n    throw new Error(`\"${value}\" does not match ${String(regExp)}`);\n  }\n\n  return value;\n};\n\nconst myRoute = route({\n  path: \":id\",\n  // 'string()' only accepts validators that return strings.\n  params: { id: string(regExp(/\\d+/)) },\n  // 'number()' only accepts validators that return numbers.\n  searchParams: { page: number(integer) },\n});\n```\n\n\u003c/details\u003e\n\n### Use Zod\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\n```tsx\nimport { route } from \"react-router-typesafe-routes\";\nimport { zod } from \"react-router-typesafe-routes/zod\";\nimport { z } from \"zod\";\n\nconst myRoute = route({\n  path: \":id\",\n  // Wrapping quotes in serialized values are omitted where possible.\n  params: { id: zod(z.string().uuid()) },\n});\n```\n\n\u003e ❗Zod doesn't do coercion by default, but you may need it for complex values returned from `JSON.parse` (for instance, a date wrapped in an object).\n\n\u003c/details\u003e\n\n### Use Yup\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\n```tsx\nimport { route } from \"react-router-typesafe-routes\";\nimport { yup } from \"react-router-typesafe-routes/yup\";\nimport { string } from \"yup\";\n\nconst ROUTE = route({\n  path: \":id\",\n  // Wrapping quotes in serialized values are omitted where possible.\n  params: { id: yup(string().uuid()) },\n});\n```\n\n\u003c/details\u003e\n\n### Integrate third-party validation library\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\n```tsx\nimport { type, parser, Type, ParserHint } from \"react-router-typesafe-routes\";\n// Some abstract third-party library.\nimport { v, Schema } from \"third-party-library\";\n\nfunction valid\u003cT\u003e(schema: Schema\u003cT\u003e): Type\u003cT\u003e {\n  return type(\n    // Library-specific validation logic.\n    (value: unknown) =\u003e schema.validate(value),\n    // You could also supply a custom parser.\n    parser(getTypeHint(schema)),\n  );\n}\n\nfunction getTypeHint(schema: Schema): ParserHint {\n  // Type hint is determined based on the schema type, excluding 'undefined'.\n  return schema.type;\n}\n\nconst myRoute = route({\n  path: \":id\",\n  params: { id: valid(v.string().uuid()) },\n});\n```\n\n\u003c/details\u003e\n\n### Create a custom parser\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\n```tsx\nimport { Parser, ParserHint, ParserType } from \"react-router-typesafe-routes\";\n\n// Extend built-in 'ParserHint' if needed.\ntype CustomParserHint = ParserHint | \"entity\";\n\n// If 'ParserHint' is extended, you need to extend 'ParserType' as well.\ntype CustomParserType\u003cT extends CustomParserHint\u003e = T extends \"entity\"\n  ? { id: number }\n  : ParserType\u003cExclude\u003cT, \"entity\"\u003e\u003e;\n\n// This factory can be used in place of built-in 'parser()'\nfunction customParser\u003cT extends CustomParserHint\u003e(\n  defaultHint?: T,\n): Parser\u003cCustomParserType\u003cT\u003e, CustomParserHint\u003e {\n  return {\n    stringify(value, { hint, kind }) {\n      const resolvedHint = hint ?? defaultHint;\n\n      // Customize serialization based on 'resolvedHint' and 'kind'.\n\n      return JSON.stringify(value);\n    },\n    parse(value, { hint, kind }) {\n      const resolvedHint = hint ?? defaultHint;\n\n      // Customize parsing based on 'resolvedHint' and 'kind'.\n\n      return JSON.parse(value) as unknown;\n    },\n  };\n}\n```\n\n\u003c/details\u003e\n\n### Construct type objects manually to cover obscure use cases\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to expand\u003c/summary\u003e\n\n```tsx\nimport { route, PathnameType } from \"react-router-typesafe-routes\";\n\n// This type accepts 'string | number | boolean' and returns 'string'.\n// It only implements 'PathnameType', so it can only be used for pathname params.\n// Implement 'SearchType', 'HashType', and 'StateType' to cover other cases.\nconst looseString: PathnameType\u003cstring, string | number | boolean\u003e = {\n  serializeParam(value) {\n    // It's always guaranteed that value is not 'undefined' here.\n    return String(value);\n  },\n  deserializeParam(value) {\n    // You could treat 'undefined' in a special way to distinguish absent and invalid params.\n    if (typeof value !== \"string\") {\n      throw new Error(\"Expected string\");\n    }\n\n    return value;\n  },\n};\n\nconst myRoute = route({\n  path: \":id\",\n  params: { id: looseString },\n});\n```\n\n\u003c/details\u003e\n\n## Concepts\n\n### Nesting\n\n#### Library routes\n\nAny route can be a child of another route. Child routes inherit everything from their parent.\n\nMost of the time, it's easier to simply inline child routes:\n\n```tsx\nimport { route } from \"react-router-typesafe-routes\";\n\nconst user = route({ path: \"user/:id\", children: { details: route({ path: \"details\" }) } });\n\nconsole.log(user.$path()); // \"/user/:id\"\nconsole.log(user.details.$path()); // \"/user/:id/details\"\n```\n\nThey can also be uninlined, most likely for usage in multiple places:\n\n```tsx\nimport { route } from \"react-router-typesafe-routes\";\n\nconst details = route({ path: \"details\" });\n\nconst user = route({ path: \"user/:id\", children: { details } });\nconst post = route({ path: \"post/:id\", children: { details } });\n\nconsole.log(user.details.$path()); // \"/user/:id/details\"\nconsole.log(post.details.$path()); // \"/post/:id/details\"\nconsole.log(details.$path()); // \"/details\"\n```\n\nTo reiterate, `details` and `user.details` are separate routes, which will usually behave differently. `details` doesn't know anything about `user`, but `user.details` does. `details` is a standalone route, but `user.details` is a child of `user`.\n\n\u003e [!WARNING]  \n\u003e Child routes can't start with `$` to prevent overlapping with route API.\n\n#### Using library routes in React Router routes\n\nRoutes structure _usually_ corresponds to the structure of `\u003cRoute /\u003e` components:\n\n```tsx\nimport { Route, Routes } from \"react-router-dom\";\n\n\u003cRoutes\u003e\n  {/* '/user/:id' */}\n  \u003cRoute path={user.$path()} element={\u003cUser /\u003e}\u003e\n    {/* '/user/:id/details' */}\n    \u003cRoute path={user.details.$path()} element={\u003cUserDetails /\u003e} /\u003e\n  \u003c/Route\u003e\n\u003c/Routes\u003e;\n```\n\n\u003e [!NOTE]  \n\u003e As a reminder, you have to render an `\u003cOutlet /\u003e` in the parent component.\n\nHowever, nothing stops you from specifying additional routes as you see fit.\n\nNote that we're using `path()` here, which returns an absolute path pattern by default. React Router allows absolute child route paths if they match the parent path.\n\nYou're encouraged to use absolute path patterns whenever possible because they are easier to reason about.\n\n\u003e [!WARNING]  \n\u003e At the time of writing, there are [quirks](https://github.com/remix-run/react-router/issues/9925) with optional path segments that may force the use of relative path patterns.\n\nRelative paths can be used like this:\n\n```tsx\nimport { Route, Routes } from \"react-router-dom\";\n\n\u003cRoutes\u003e\n  {/* 'user/:id' */}\n  \u003cRoute path={user.$path({ relative: true })} element={\u003cUser /\u003e}\u003e\n    {/* 'details' */}\n    \u003cRoute path={user.$.details.$path({ relative: true })} element={\u003cUserDetails /\u003e} /\u003e\n  \u003c/Route\u003e\n\u003c/Routes\u003e;\n```\n\nThat is, the `$` property of every route contains child routes that lack parent path pattern. The mental model here is that `$` defines the path pattern start.\n\n`$path()` is a combined path with a leading slash (`/`), and `$path({ relative: true })` is a combined path **without intermediate stars (`*`)** and without a leading slash (`/`).\n\n#### Nested `\u003cRoutes /\u003e`\n\nIf your `\u003cRoute/\u003e` is rendered in a nested `\u003cRoutes /\u003e`, you have to not only add a `*` to the parent path, but also exclude the parent path from the subsequent paths. This might change if [this proposal](https://github.com/remix-run/react-router/discussions/9841) goes through.\n\n```tsx\nimport { Route, Routes } from \"react-router-dom\";\nimport { route } from \"react-router-typesafe-routes\";\n\nconst user = route({ path: \"user/:id/*\", children: { details: route({ path: \"details\" }) } });\n\n\u003cRoutes\u003e\n  {/* '/user/:id/*' */}\n  \u003cRoute path={user.$path()} element={\u003cUser /\u003e} /\u003e\n\u003c/Routes\u003e;\n\n// Somewhere inside \u003cUser /\u003e\n\u003cRoutes\u003e\n  {/* '/details' */}\n  \u003cRoute path={user.$.details.$path()} element={\u003cUserDetails /\u003e} /\u003e\n\u003c/Routes\u003e;\n```\n\n\u003e [!NOTE]  \n\u003e Star doesn't prevent subsequent routes from being rendered as direct children.\n\n\u003e [!WARNING]  \n\u003e At the time of writing, there are [some issues](https://github.com/remix-run/react-router/issues/9929) with nested `\u003cRoutes /\u003e` if dynamic segments are used.\n\n### Typing\n\n#### Terminology\n\nParams can undergo the following transformations:\n\n- _Serialization_ - a process of converting a JS value into a string (for URL params) or a serializable format (for state).\n- _Deserialization_ - a process of transforming a string (for URL params) or a serializable value (for state) into a more structured format and checking its type and restrictions. For flexibility, this value may be different from the value that was serialized (e.g. `number` can be converted into `string`).\n\nFor serializable params, these transformations can be split into the following:\n\n- _Stringification_ - a process of converting a serializable value into a string, the final step of _Serialization_ (for URL params).\n- _Parsing_ - the opposite of _Stringification_ and the first step of _Deserialization_ (for URL params). For convenience, it's not required to return exactly the same value that was stringified, because the result of parsing is always validated.\n- _Validation_ - a process of checking a value type and restrictions, the final step of _Deserialization_. For convenience, it can change the value to make it valid.\n\n#### Built-in types\n\nBuilt-in types are only concerned about _stringification_, _parsing_, and _validation_. They use `Parser` and `Validator` for that.\n\n##### `Parser`\n\nParser is simply a group of functions for transforming a value to `string` and back:\n\n```typescript\ninterface Parser\u003cT, THint extends string = never\u003e {\n  stringify: (value: T, context: ParserContext\u003cTHint\u003e) =\u003e string;\n  // Return value can be different from T in some edge cases. It's always validated anyway.\n  parse: (value: string, context: ParserContext\u003cTHint\u003e) =\u003e unknown;\n}\n\ninterface ParserContext\u003cTHint extends string = never\u003e {\n  // This field is used to change the behavior of the parser dynamically.\n  hint?: THint;\n  // This field isn't used by the library, but you can use it in custom parsers.\n  kind: \"pathname\" | \"search\" | \"hash\";\n}\n```\n\nThe library provides the `parser()` helper for accessing the built-in parser. It can accept an optional type hint. By default, it simply behaves as `JSON`. It also has a special behavior for strings and dates, where it omits wrapping quotes in such serialized values.\n\n##### `Validator`\n\nValidator is simply a function for validating values:\n\n```typescript\ninterface Validator\u003cT, TPrev = unknown\u003e {\n  (value: TPrev): T | undefined;\n}\n```\n\nIt returns a valid value or throws (or returns `undefined`) if that's impossible. It can transform values to make them valid.\n\nThe important thing is that it has to handle both the original value and whatever the corresponding parser returns.\n\n##### Base helper\n\nThe `type()` helper is a built-in type that all other built-in types are based on. The resulting param type is inferred from the given validator.\n\n```typescript\nimport { type, parser, Validator } from \"react-router-typesafe-routes\";\n\nconst positiveNumber: Validator\u003cnumber\u003e = (value: unknown): number =\u003e {\n  if (typeof value !== \"number\" || value \u003c= 0) {\n    throw new Error(\"Expected positive number\");\n  }\n\n  return value;\n};\n\n// The following types are equivalent (JSON is used as a parser).\n// You could also supply a custom parser.\ntype(positiveNumber, parser(\"unknown\"));\ntype(positiveNumber, parser());\ntype(positiveNumber);\n```\n\nBy default, if deserialization results in `undefined` or throws, `undefined` is returned. This can be changed by the following modifiers:\n\n```typescript\n// This will throw an error.\ntype(positiveNumber).defined();\n// This will return the given value.\ntype(positiveNumber).default(1);\n```\n\nThe `.defined()`/`.default()` modifiers guarantee that the parsing result is not `undefined`. Default values passed to `.default()` are validated.\n\nYou can also make an array:\n\n```typescript\n// Deserialization will always return 'number[]'.\n\n// Absent/invalid values will be omitted.\ntype(positiveNumber).array();\n\n// Absent/invalid values will be replaced with '-1'.\ntype(positiveNumber).default(-1).array();\n\n// Absent/invalid values will lead to an error.\ntype(positiveNumber).defined().array();\n```\n\nArrays can only be used in search params and state, because there is no standard way to store arrays in pathname params or hash. For state, if a value is not an array, it's parsed as an empty array.\n\n##### Type-specific helpers\n\nFor simple cases, you can use type-specific helpers: `string()`, `number()`, `boolean()`, and `date()`. They are built on top of `type()` and have the corresponding parsers and type checks built-in, at the same time allowing to customize both of them.\n\nThere is also somewhat specific `union()` helper that accepts an enum (or an enum-like object) or an array instead of a validator.\n\n##### Third-party validation libraries\n\nIf you can, you should use a validation library for all types. You can use Zod and Yup out of the box via the `zod()` and `yup()` helpers, and you should be able to integrate any third-party validation library via the `type()` helper. See [Advanced examples](#advanced-examples).\n\n#### Type objects\n\nUnder the hood, built-in types create type objects that describe how to serialize and deserialize params:\n\n```typescript\n// Can be used for pathname params\ninterface PathnameType\u003cTOut, TIn = TOut\u003e {\n  serializeParam: (originalValue: Exclude\u003cTIn, undefined\u003e) =\u003e string;\n  deserializeParam: (plainValue: string | undefined) =\u003e TOut;\n}\n\n// Can be used for search params\ninterface SearchType\u003cTOut, TIn = TOut\u003e {\n  serializeSearchParam: (originalValue: Exclude\u003cTIn, undefined\u003e) =\u003e string[] | string;\n  deserializeSearchParam: (plainValue: string[]) =\u003e TOut;\n}\n\n// Can be used for hash\ninterface HashType\u003cTOut, TIn = TOut\u003e {\n  serializeHash: (originalValue: Exclude\u003cTIn, undefined\u003e) =\u003e string;\n  deserializeHash: (plainValue: string) =\u003e TOut;\n}\n\n// Can be used for state fields or the whole state\ninterface StateType\u003cTOut, TIn = TOut\u003e {\n  serializeState: (originalValue: Exclude\u003cTIn, undefined\u003e) =\u003e unknown;\n  deserializeState: (plainValue: unknown) =\u003e TOut;\n}\n```\n\n\u003e [!NOTE]  \n\u003e It's guaranteed that `undefined` will never be passed as `originalValue`.\n\nThere are some limitations in type objects that can be produced by built-in types, for instance:\n\n- `TOut` is basically the same as `TIn` (the only difference is that `undefined` is added to `TOut` as needed).\n- Arrays are somewhat limited.\n- Errors and `undefined` values can't be distinguished during deserialization.\n- Input values must be serializable.\n\nNormally these limitations shouldn't get in your way, but if they do, you can get yourself unstuck by creating type objects manually.\n\n#### Pathname params\n\nPathname params are inferred from the provided path pattern and can be overridden (partially or completely) with pathname type objects.\n\nJust as usual segments, dynamic segments (pathname params) can be made optional by adding a `?` to the end. This doesn't apply to star (`*`) segments though.\n\nInferred params will implicitly use `string().defined()` and `string()` for required and optional params respectively.\n\n```tsx\nimport { route, number } from \"react-router-typesafe-routes\";\n\n// Here, id is overridden to be a number, and subId and optionalId are strings\nconst myRoute = route({\n  path: \"route/:id/:subId/:optionalId?\",\n  params: { id: number() },\n});\n```\n\nUpon building, pathname params are required or optional based on the `?` modifier, except for the star parameter (`*`), which is always optional upon building.\n\nParsing behavior is determined by the type objects. Note that React Router parses star parameter (`*`) as an empty string if there are no segments to match.\n\n\u003e [!NOTE]  \n\u003e You most likely will never need it, but it's technically possible to provide a type object for the star parameter as well.\n\n#### Search params\n\nSearch params are determined by the provided search type objects.\n\n```tsx\nimport { route, string } from \"react-router-typesafe-routes\";\n\n// Here, we define a search parameter 'filter' of 'string' type\nconst myRoute = route({ path: \"route\", searchParams: { filter: string() } });\n```\n\nUpon building, all search params are optional. Parsing behavior is determined by the type objects.\n\n#### Hash\n\nHash is determined by the provided hash type object. It's also possible to provide an array of possible `string` values if you want to inherit parent values.\n\n```tsx\nimport { route, string, union } from \"react-router-typesafe-routes\";\n\nconst routeWithAnyHash = route({ path: \"route\", hash: string() });\n\nconst routeWithRestrictedHash = route({ path: \"route\", hash: union([\"about\", \"more\"]) });\n\nconst routeWithInheritableValues = route({\n  path: \"route\",\n  hash: [\"about\", \"more\"],\n});\n```\n\nUpon building, hash is optional. Parsing behavior is determined by the type object. In the case of an array of possible values, an absent/invalid value will result in `undefined`.\n\n#### State fields\n\nState fields are determined by the provided state type objects. It's also possible to use a type object to define the whole state.\n\n```tsx\nimport { route, boolean, string } from \"react-router-typesafe-routes\";\n\n// Here, we define a state field 'fromList' of 'boolean' type\nconst myRoute = route({ path: \"route\", state: { fromList: boolean() } });\n\n// Here, we define the whole state as 'string'\nconst myOtherRoute = route({ path: \"route\", state: string() });\n```\n\nUpon building, all state fields (and the whole state) are optional. Parsing behavior is determined by the type objects.\n\n#### Types inheritance\n\nChild routes inherit all type objects from their parent. For params with the same name, child type objects take precedence.\n\nSeparate hash values can be inherited only if they are defined as an array of strings.\n\n#### Types composition\n\nPathless routes can be composed to other routes to share types. Please refer to [Advanced examples: Share types between routes](#share-types-between-routes).\n\nMultiple routes can be composed. For params with the same name, the rightmost route takes precedence.\n\n#### Types priority\n\nWhen there are multiple types for the same param, they are resolved as follows, from the lowest priority to the highest:\n\n- Implicit pathname types\n- Inherited types\n- Composed types\n- Explicit types\n\nIf hash type is defined as an array of strings and a hash type at the same time, the hash type always wins regardless of the rules above.\n\nIf state type is defined as a set of its fields' types and a whole state type at the same time, the whole state type always wins regardless of the rules above.\n\n\u003e [!WARNING]  \n\u003e Params with the same name are discouraged.\n\n## API\n\n### `route()`\n\nA route is defined via the `route()` helper. All its options are optional.\n\n```tsx\nimport { route, string, number, boolean } from \"react-router-typesafe-routes\";\n\nconst myFragment = route({ searchParams: { myFragmentParam: string() } });\n\nconst myRoute = route({\n  path: \"my/path\",\n  compose: [myFragment],\n  params: { myPathnameParam: string() },\n  searchParams: { mySearchParam: number() },\n  hash: union([\"my-hash\", \"my-other-hash\"]),\n  state: { myStateParam: boolean() },\n  children: { myChildRoute: route({ path: \"child\" }) },\n});\n```\n\nThe `path` option is a path pattern that you would put to the `path` property of a `\u003cRoute/\u003e`, but without leading or trailing slashes (`/`). More specifically, it can:\n\n- be a simple segment or a group of segments (`'user'`, `'user/details'`).\n- have any number of dynamic segments (params) anywhere (`':id/user'`, `'user/:id/more'`).\n- **end** with a star segment (`'user/:id/*'`, `'*'`)\n- have any number of optional segments (`user?/:id?`)\n- be an empty string (`''`).\n\nUnspecified (or `undefined`) `path` means that the route is pathless. Pathless routes are intended for types sharing.\n\nThe `compose` option is an array of pathless routes whose types are composed into the route. See [Typing: Types composition](#types-composition).\n\nThe `params`, `searchParams`, `hash`, and `state` options specify type objects (and possibly hash values) of the route. See [Typing](#typing).\n\nThe `children` option specifies child routes of the route. See [Nesting](#nesting).\n\nThe `route()` helper returns a route object, which has the following fields:\n\n- `$path()` that returns an absolute path pattern (by default) or a relative path pattern (if the `relative` option is set to `true`). Absolute path pattern is a combined pattern with a leading slash (`/`), and relative path pattern is a combined pattern **without intermediate stars (`*`)** and a leading slash (`/`). They can be passed to e.g. the `path` prop of React Router `\u003cRoute/\u003e`.\n- `$buildPath()` for building parametrized URL paths (pathname + search + hash) which can be passed to e.g. the `to` prop of React Router `\u003cLink /\u003e`.\n- `$buildState()` for building typed states, which can be passed to e.g. the `state` prop of React Router `\u003cLink /\u003e`.\n- `$buildPathname()`, `$buildSearch()`, and `$buildHash()` for building parametrized URL parts. They can be used (in conjunction with `$buildState()`) to e.g. build a parametrized `Location` object.\n- `$deserializeParams()`, `$deserializeSearchParams()`, `$deserializeHash()`, and `$deserializeState()` for retrieving typed params from React Router primitives. Untyped params are omitted.\n- `$serializeParams()` and `$serializeSearchParams()` for building React Router primitives from typed params. Note how hash and state don't need these functions because `$buildHash()` and `$buildState()` can be used instead.\n- `$spec`, which contains resolved type objects (and possibly hash values) of the route, as well as its `path` option.\n- `$`, which contains child routes that lack the parent path pattern.\n- Any number of child routes (that can't start with a `$`).\n\n`$buildPath()` and `$buildPathname()` accept the `relative` option for building relative paths.\n\n`$buildPath()`, `$buildSearch()`, and `$serializeSearchParams()` accept a `URLSearchParams` object in the `untypedSearchParams` option for mixing in its untyped params.\n\n`$buildState()` accepts a state object in the `untypedState` option for mixing in its untyped fields.\n\n### `parser()`\n\nThe built-in parser is exposed as `parser()`. It should only be used for creating custom wrappers around `type()`.\n\nIt accepts the following type hints:\n\n- `'unknown'` - the value is processed by `JSON`. This is the default.\n- `'string'` - the value is not transformed in any way.\n- `'date'` - the value is transformed to an ISO string.\n- `'number'` and `'boolean'`, which behave identically to `'unknown'` and exist only for technical reasons.\n\n### `type()`\n\nAll type helpers are wrappers around `type()`. It's primarily exposed for integrating third-party validation libraries, but it can also be used directly, if needed.\n\nSee [Typing: Base helper](#base-helper).\n\nThere are built-in helpers for common types:\n\n- `string()`, `number()`, `boolean()`, `date()` - simple wrappers around `type()`, embed the corresponding parsers and type checks. Can accept validators that expect the corresponding types as an input and/or custom parsers.\n- `union()` - a wrapper around `type()` that describes unions of `string`, `number`, or `boolean` values. Accepts a readonly array or an enum (or an enum-like readonly (`as const`) object). Can accept a custom parser as well.\n\nThere are also built-in helpers for third-party validation libraries:\n\n- `zod()` - a wrapper around `type()` for creating type objects based on Zod Types. Uses a separate entry point: `react-router-typesafe-routes/zod`.\n- `yup()` - a wrapper around `type()` for creating type objects based on Yup Schemas. Uses a separate entry point: `react-router-typesafe-routes/yup`.\n\nAll of them use the built-in parser with auto-detected hint by default, and all of them allow to supply a custom parser.\n\nAll built-in helpers catch parsing and validation errors and replace them with `undefined`. This behavior can be altered with the following modifiers:\n\n- `.default()` - accepts a default value that is used instead of an absent/invalid param;\n- `.defined()` - specifies that an error is thrown in case of an absent/invalid param. For invalid params, the original error is used.\n\n### `configure()`\n\nAll entry points expose the `configure()` helper that sets a parser for the corresponding type helpers globally. It accepts a parser factory like the built-in `parser()`.\n\n### Useful types\n\n- `Route` is a base type that any route object is assignable to.\n- `PathParam` is similar to `PathParam` from React Router, but it allows a slightly more nuanced params extraction.\n- `InPathnameParams`, `OutPathnameParams`, `InSearchParams`, `OutSearchParams`, `InState`, `OutState`, `InHash`, and `OutHash` can be used to extract the corresponding params from the route spec (`$spec`).\n\n### Hooks\n\nAll hooks are designed in such a way that they can be reimplemented in the userland. If something isn't working for you, you can get yourself unstuck by creating custom hooks.\n\nOf course, you can still use React Router hooks as you see fit.\n\n#### `useTypedParams()`\n\nThe `useTypedParams()` hook is a thin wrapper around React Router `useParams()`. It accepts a route object as the first parameter, and the rest of the API is basically the same, but everything is properly typed.\n\n#### `useTypedSearchParams()`\n\nThe `useTypedSearchParams()` hook is a (somewhat) thin wrapper around React Router `useSearchParams()`. It accepts a route object as the first parameter, and the rest of the API is basically the same, but everything is properly typed.\n\nThe only notable difference is that `setTypedSearchParams()` has an additional `untypedSearchParams` option. If `true`, existing untyped (by the given route) search params will remain intact. Note that this option does not affect the `state` option. That is, there is no way to preserve untyped state fields.\n\nThe reason for this is that `useTypedSearchParams()` is intended to be a simple wrapper around `useSearchParams()`, and the latter doesn't provide any access to the current state. If [this proposal](https://github.com/remix-run/react-router/discussions/9950) goes through, it would be very easy to implement, but for now, the only way to achieve this is to create a custom hook.\n\n#### `useTypedHash()`\n\nThe `useTypedHash()` hook is a thin wrapper around React Router `useLocation()`. It accepts a route object as the first parameter and returns a typed hash.\n\n#### `useTypedState()`\n\nThe `useTypedState()` hook is a thin wrapper around React Router `useLocation()`. It accepts a route object as the first parameter and returns a typed state.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffenok%2Freact-router-typesafe-routes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffenok%2Freact-router-typesafe-routes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffenok%2Freact-router-typesafe-routes/lists"}