DEV Community

Leonid Fenko
Leonid Fenko

Posted on

 

React Router Typesafe Routes v1.0.0 is Here πŸŽ‰

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:

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?") }
    ),
};
Enter fullscreen mode Exit fullscreen mode

Use Route components as usual:

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>;
Enter fullscreen mode Exit fullscreen mode

Use Link components as usual:

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>;
Enter fullscreen mode Exit fullscreen mode

Get typed path params with useTypedParams():

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);
Enter fullscreen mode Exit fullscreen mode

Get typed search params with useTypedSearchParams():

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);
Enter fullscreen mode Exit fullscreen mode

Get typed state with useTypedState():

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);
Enter fullscreen mode Exit fullscreen mode

Get typed hash with useTypedHash():

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);
Enter fullscreen mode Exit fullscreen mode

A detailed description is available at the project page.

Oldest comments (0)

typescript

11 Tips That Make You a Better Typescript Programmer

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!