Giter Site home page Giter Site logo

react-chicane's Introduction

react-chicane

mit licence npm version bundlephobia

A simple and safe router for React and TypeScript.

Installation

yarn add react-chicane

Run the example

git clone [email protected]:zoontek/react-chicane.git
cd react-chicane/example
yarn install && yarn dev

๐Ÿ“˜ Usage

Creating a router

This library exports a main function: createRouter. The goal behind this is to enforce listing all your project routes using fancy names in a file and use the strongly typed methods returned.

import { createRouter } from "react-chicane";

const { useRoute } = createRouter({
  root: "/",
  users: "/users",
  user: "/users/:userId",
});

const App = () => {
  const route = useRoute(["root", "users", "user"]);

  if (!route) {
    return <h1>404</h1>;
  }

  // route object is a discriminated union
  switch (route.name) {
    case "root":
      return <h1>Homepage</h1>;
    case "users":
      return <h1>Users</h1>;
    case "user":
      // params are strongly typed
      return <h1>User {route.params.userId}</h1>;
  }
};

๐Ÿ‘‰ Note: Even if you can use classic type guards (if, switch, etc.) to check the result, I strongly recommand using a pattern matching library, like the excellent ts-pattern (all the following examples will).

โœ๏ธ Path syntax

react-chicane doesn't bother about what's inside your path, your search params or your hash. It only exposes an object, params.

  • A param in your path will result in a required string
  • A param in your search or your hash will result in an optional string
  • A mutiple param in your search will result in a optional string[]
import { createRouter } from "react-chicane";
import { match } from "ts-pattern";

export const { useRoute } = createRouter({
  groups: "/groups",
  group: "/groups/:groupId?:foo&:bar[]#:baz",
  users: "/groups/:groupId/users",
  user: "/groups/:groupId/users/:userId",
  // it also supports wildcard routes!
  usersArea: "/groups/:groupId/users/*",
});

const App = () => {
  const route = useRoute(["groups", "group", "users", "user"]);

  match(route)
    .with({ name: "groups" }, ({ params }) => console.log(params)) // {}
    .with({ name: "group" }, ({ params }) => console.log(params)) // { groupId: string, foo?: string, bar?: string[], baz?: string }
    .with({ name: "users" }, ({ params }) => console.log(params)) // { groupId: string }
    .with({ name: "user" }, ({ params }) => console.log(params)) // { groupId: string, userId: string }
    .otherwise(() => <h1>404</h1>);

  // โ€ฆ
};

๐Ÿ‘‰ Note: Non-param search and hash are not supported.

๐Ÿ”— Creating URLs

Because it's nice to create safe internal URLs, createRouter also returns createURL.

import { createRouter } from "react-chicane";

const { createURL } = createRouter({
  root: "/",
  users: "/users",
  user: "/users/:userId",
});

createURL("root"); // -> "/"
createURL("users"); // -> "/users"
createURL("user", { userId: "zoontek" }); // -> "/users/zoontek"

โš™๏ธ API

createRouter

Create a router instance for your whole application.

import { createRouter } from "react-chicane";

const Router = createRouter(
  {
    root: "/",
    users: "/users",
    user: "/users/:userId",
  },
  {
    basePath: "/setup/basePath/here", // Will be prepend to all your paths (optional)
    blockerMessage: "Are you sure you want to leave this page?", // A default navigation blocker message (optional)
  },
);

๐Ÿ‘‡ Note: All the following examples will use this Router instance.

Router.location

type Location = {
  url: string;
  path: string[];
  search: Record<string, string | string[]>;
  hash?: string;
};

Router.location; // Location

Router.navigate

Navigate to a given route.

Router.navigate("root");
Router.navigate("users");
Router.navigate("user", { userId: "zoontek" });

Router.replace

Same as navigate, but will replace the current route in the browser history.

Router.replace("root");
Router.replace("users");
Router.replace("user", { userId: "zoontek" });

Router.goBack

Go back in browser history.

Router.goBack();

Router.goForward

Go forward in browser history.

Router.goForward();

Router.createURL

Safely create internal URLs.

Router.createURL("root"); // -> "/"
Router.createURL("users"); // -> "/users"
Router.createURL("user", { userId: "zoontek" }); // -> "/users/zoontek"

Router.useRoute

Listen and match a bunch of your routes. Awesome with pattern matching.

import { match } from "ts-pattern";

const App = () => {
  // The order isn't important, paths are ranked using https://reach.tech/router/ranking
  const route = Router.useRoute(["root", "users", "user"]);

  return match(route)
    .with({ name: "root" }, () => <h1>root</h1>)
    .with({ name: "users" }, () => <h1>users</h1>)
    .with({ name: "user" }, ({ params: { groupId } }) => <h1>user</h1>)
    .otherwise(() => <h1>404</h1>);
};

Router.useLink

As this library doesn't provide a single component, we expose this hook to create your own customized Link.

const Link = ({
  children,
  to,
  replace,
  target,
}: {
  children?: React.ReactNode;
  to: string;
  replace?: boolean;
  target?: React.HTMLAttributeAnchorTarget;
}) => {
  const { active, onClick } = useLink({ href: to, replace, target });

  return (
    <a
      href={to}
      onClick={onClick}
      target={target}
      style={{ fontWeight: active ? 700 : 400 }}
    >
      {children}
    </a>
  );
};

// usage
<Link to={Router.createURL("user", { userId: "zoontek" })}>Profile</Link>;

Router.useLocation

Listen and react on Router.location changes.

const App = () => {
  const location: Location = Router.useLocation();

  React.useEffect(() => {
    console.log("location changed", location);
  }, [location]);

  // โ€ฆ
};

Router.useBlocker

Block the navigation and ask user for confirmation. Useful to avoid loosing a form state. It accepts a second paramater if you want to override the default blockerMessage.

const App = () => {
  const { formStatus } = useForm(/* โ€ฆ */);

  Router.useBlocker(
    formStatus === "editing",
    "Are you sure you want to stop editing this profile?",
  );

  // โ€ฆ
};

Router.subscribe

Subscribe to location changes. Useful to reset keyboard focus.

const App = () => {
  React.useEffect(() => {
    const unsubscribe = Router.subscribe((location: Location) => {
      resetKeyboardFocusToContent();
    });

    return unsubscribe;
  }, []);

  // โ€ฆ
};

Router.unsafeNavigate and Router.unsafeReplace

Two methods similar to Router.navigate and Router.replace but which accept a string as unique argument. Useful for escape hatches.

A quick example with a Redirect component:

const Redirect = ({ to }: { to: string }) => {
  const { url } = Router.useLocation();

  React.useLayoutEffect(() => {
    if (to !== url) {
      Router.unsafeReplace(to);
    }
  }, []);

  return null;
};

// usage
<Redirect to={Router.createURL("root")} />;

encodeSearch and decodeSearch

Encode and decode url search parameters.

import { encodeSearch, decodeSearch } from "react-chicane";

encodeSearch({ invitation: "542022247745", users: ["frank", "chris"] });
// -> "?invitation=542022247745&users=frank&users=chris"

decodeSearch("?invitation=542022247745&users=frank&users=chris");
// -> { invitation: "542022247745", users: ["frank", "chris"] }

๐Ÿ‘ทโ€โ™‚๏ธ Roadmap

  • Improve documentation
  • Tests, tests, tests
  • Switch to useSyncExternalStore (React 18+)
  • Write a "focus reset" recipe
  • Find a cool logo
  • Create a website (?)

๐Ÿ™Œ Acknowledgements

react-chicane's People

Contributors

zoontek avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.