React Router Typesafe Routes v1.0.0 is Here 🎉
Tags: react, typescript, showdev
Published: 2023-01-23 21:28:08

[React Router Typesafe Routes](https://github.com/fenok/react-router-typesafe-routes) was updated to solve all issues emerged during its usage on several real-life projects and support relevant new features of React Router.

The library allows defining routes that provide type safety for path params, search params, state, and hash without sacrificing any features of React Router v6.

Params serialization and parsing are fully customizable. You can also validate params upon parsing, and you have control over how parsing errors are processed.

To save you a click, here is how it can be used:

```tsx
import { route, numberType, booleanType, hashValues, throwable } from "react-router-typesafe-routes/dom"; // Or /native

const ROUTES = {
    USER: route(
        // This is a normal path pattern, but without leading or trailing slashes.
        // By default, path params are inferred from the pattern.
        "user/:id",
        {
            // We can override some or all path params.
            // We override id and specify that a parsing error will be thrown.
            params: { id: numberType(throwable) },
            // These are search params.
            // We specify a fallback to use in case of a parsing error.
            searchParams: { infoVisible: booleanType(false) },
            // These are state fields, which are similar to search params.
            // We use default parsing behavior, so a parsing error will result in undefined.
            state: { fromUserList: booleanType },
            // These are allowed hash values.
            // We could also use hashValues() to indicate that any hash is allowed.
            hash: hashValues("info", "comments"),
        },
        // This is a child route, which inherits all parent params.
        // Note how it has to start with an uppercase letter.
        { DETAILS: route("details/:lang?") }
    ),
};
```

Use `Route` components as usual:

```tsx
import { Route, Routes } from "react-router-dom"; // Or -native
import { ROUTES } from "./path/to/routes";

// Absolute paths
<Routes>
    {/* /user/:id */}
    <Route path={ROUTES.USER.path} element={<User />}>
        {/* /user/:id/details/:lang? */}
        <Route path={ROUTES.USER.DETAILS.path} element={<UserDetails />} />
    </Route>
</Routes>;

// Relative paths
<Routes>
    {/* user/:id */}
    <Route path={ROUTES.USER.relativePath} element={<User />}>
        {/* details/:lang? */}
        {/* $ effectively defines path pattern start. */}
        <Route path={ROUTES.USER.$.DETAILS.relativePath} element={<UserDetails />} />
    </Route>
</Routes>;
```

Use `Link` components as usual:

```tsx
import { Link } from "react-router-dom"; // Or -native
import { ROUTES } from "./path/to/routes";

// Absolute link
<Link
    // Path params: { id: number; lang?: string } -- optionality is governed by the path pattern.
    // Search params: { infoVisible?: boolean }.
    // State fields: { fromUserList?: boolean }.
    // Hash: "info" | "comments" | undefined
    to={ROUTES.USER.DETAILS.buildPath({ id: 1, lang: "en" }, { infoVisible: false }, "comments")}
    state={ROUTES.USER.DETAILS.buildState({ fromUserList: true })}
>
    /user/1/details/en?infoVisible=false#comments
</Link>;

// Relative link
<Link
    // Path params: { lang?: string } -- optionality is governed by the path pattern.
    // Other params remain the same.
    // $ effectively defines path pattern start.
    to={ROUTES.USER.$.DETAILS.buildRelativePath({ lang: "en" }, { infoVisible: true }, "info")}
    state={ROUTES.USER.DETAILS.buildState({ fromUserList: false })}
>
    details/en?infoVisible=true#info
</Link>;
```

Get typed path params with `useTypedParams()`:

```tsx
import { useTypedParams } from "react-router-typesafe-routes/dom"; // Or /native
import { ROUTES } from "./path/to/routes";

// The type here is { id: number; lang?: string }.
// Note how id can't be undefined because a parsing error will be thrown.
const { id, lang } = useTypedParams(ROUTES.USER.DETAILS);
```

Get typed search params with `useTypedSearchParams()`:

```tsx
import { useTypedSearchParams } from "react-router-typesafe-routes/dom"; // Or /native
import { ROUTES } from "./path/to/routes";

// The type here is { infoVisible: boolean }.
// Note how infoVisible can't be undefined because a fallback will be used upon a parsing error.
const [{ infoVisible }, setTypedSearchParams] = useTypedSearchParams(ROUTES.USER.DETAILS);
```

Get typed state with `useTypedState()`:

```tsx
import { useTypedState } from "react-router-typesafe-routes/dom"; // Or /native
import { ROUTES } from "./path/to/routes";

// The type here is { fromUserList: boolean | undefined }.
// Note how fromUserList can be undefined due to returning undefined upon error.
const { fromUserList } = useTypedState(ROUTES.USER.DETAILS);
```

Get typed hash with `useTypedHash()`:

```tsx
import { useTypedHash } from "react-router-typesafe-routes/dom"; // Or /native
import { ROUTES } from "./path/to/routes";

// The type here is "info" | "comments" | undefined.
const hash = useTypedHash(ROUTES.USER.DETAILS);
```

A detailed description is available at the [project page](https://github.com/fenok/react-router-typesafe-routes#readme).