FormidableLabs / redux-little-router

A tiny router for Redux that lets the URL do the talking.
MIT License
1.04k stars 113 forks source link

How to specify custom matcher #255

Open jkillian opened 6 years ago

jkillian commented 6 years ago

Hi redux-little-router team! One of the goals of the projects, which I appreciated, was:

Route matching should be simple and extendable.

Unfortunately, I'm having a lot of trouble figuring out how to override the matching behavior. Do you have an example of how I can provide a custom matcher to the router? Thanks!

I dove into the code some, and found that being able to supply this argument would likely do the trick - but I couldn't figure out anyway to supply it:

https://github.com/FormidableLabs/redux-little-router/blob/81e85afcef374bf921979928bd106b0201dc8093/src/util/create-installer.js#L27

jkillian commented 6 years ago

As an additional note, I believe it might be possible to provide by plugging in to the createBrowserRouter function? But unfortunately that export isn't available in the latest released version yet: https://github.com/FormidableLabs/redux-little-router/blob/b60a98e61aec04c1c3962e0a8ed80e4b9c829662/src/environment/browser-router.js#L15

jkillian commented 6 years ago

With the yet unreleased version, I believe code like this should work. The main change comes at the bottom of the code - I use the raw createBrowserRouter function and pass it a custom installer which simply calls the regular installer and supplies it with a custom matcher.

Obviously not a completely ergonomic API, but I'm happy it's possible!

import * as UrlPattern from 'url-pattern';
import { createBrowserRouter } from "@jkillian/redux-little-router/lib/environment/browser-router"
import install from "@jkillian/redux-little-router/lib/install"

type RouteCache = {
  route: string,
  pattern: UrlPattern,
  result: Object
};

const eagerMatcher = (routeList: Array<RouteCache>) =>
  (incomingUrl: string) => {
    // Discard query strings
    const pathname = incomingUrl.split('?')[0];

    // Find the route that matches the URL
    for (let i = 0; i < routeList.length; i++) {
      const storedRoute = routeList[i];
      const match = storedRoute.pattern.match(pathname);

      if (match) {
        // Return the matched params and user-defined result object
        return {
          route: storedRoute.route,
          params: match,
          result: storedRoute.result
        };
      }
    }

    return null;
  };

export const createMatcher = (routes: Object) => {
  const routeList = Object.keys(routes).sort().reverse().map(route => ({
    route,
    pattern: new UrlPattern(route, {segmentValueCharset: 'a-zA-Z0-9-_~ %@.'}),
    result: routes[route]
  }));

  return eagerMatcher(routeList);
};

export const createRouter = () => {
    return createBrowserRouter((opts) => install({...opts, createMatcher}));
}