Skip to content

Releases: cher-ami/router

@cher-ami/router V3 is out 🎉

09 Feb 16:31
Compare
Choose a tag to compare

What's new?

Almost a year ago, we start to implement an SSR support to our router. After a lot of tests and projects based on beta, it was time to release this v3. this release includes a lot of codebase core's and test's refactoring due to this new feature. An SSR example as been added to this repo too.

How it works?

@cher-ami/router is compatible with SSR due to using staticLocation props instead of history props on the Router instance.
In this case, the router will match only with staticLocation props value and render the appropiate route without invoking the browser history.

<Router
  routes={routesList}
  staticLocation={"/foo"}
  // history={createBrowserHistory()}
>
  // ...
</Router>

In order to use this router on server side, we need to be able to request API on the server side too.
In this case, request will be print as javascript window object on the renderToString html server response.
So the client will get this response.

To be able to request on server side (and on client side too), getStaticProps route property is now available on the route object:

   {
    path: "/article/:slug",
    component: ArticlePage,
    name: "Article",
    getStaticProps: async (props, currentLang) => {
      // props contains route props and params (ex: slug: "article-1")
      const res = await fetch(`https://api.com/posts/${currentLang.key}/${props.params.slug}`);
      const api = await res.json();
      return { api };
    }
  }

Then, get the response data populated in page component props:

function HomePage({ api }) {
  return <div>{api.title}</div>;
}

For larger example, check the example-ssr folder.

changelog

v3.0.0-beta.16

15 Dec 14:13
Compare
Choose a tag to compare

Feature

#130 Expose currentLang in getStaticProps param

currentLang object is needed in getStaticProps route property to request URL by language.

   {
    path: "/article/:slug",
    component: ArticlePage,
    name: "Article",
    getStaticProps: async (props, currentLang) => {
      // props contains route props and params (ex: slug: "article-1")
      const res = await fetch(`https://api.com/posts/${currentLang.key}/${props.params.slug}`);
      const api = await res.json();
      return { api };
    }
  }

v3.0.0-beta.15

15 Dec 12:10
Compare
Choose a tag to compare

Feature

#129

  • create langService.redirectToBrowserLang()
  • rename langService.redirect() to langService.redirectToDefaultLang()

v3.0.0-beta.14

16 Nov 15:44
Compare
Choose a tag to compare

Fix

Fix sub-router route getStaticProps #127

v3.0.0-beta.9

13 Sep 09:52
Compare
Choose a tag to compare

v3.0.0-beta.7

29 Aug 16:29
Compare
Choose a tag to compare

feature & breaking changes

Fix/matcher sub router (#116)

routes list:

{
    path: "/about",
    component: AboutPage,
    children: [
      {
        path: "/foo",
        component: FooPage,
      },
      {
        path: "/bar",
        component: BarPage,
      },
    ],
},

In this example, we have a main router (1) and a subRouter (2) instance.

When we were in AboutPage, and the URL was /about/foo, currentRoute was returned the route object needed by Stack component and not really the current route from URL.

before, URL is "/about/foo" & log curentRoute in AboutPage:

// currentRoute obj
{
  name: "AboutPage,
  path: "/about"
  ...
}

more logical after, URL is "/about/foo" & log curentRoute in AboutPage:

// currentRoute obj
{
  name: "FooPage,
  path: "/foo"
  _context: {
     name: "AboutPage",
     path: "/about"
  }
  ...
}

The main issue is the mix between the parse URL logic (witch need to return the appropriate route object from URL) and the stack render.

currentRoute object is the result of getRouteFromUrl( url ). If url param is /about/foo, we expect to receive the object corresponding to this URL. BUT the stack render need the current "context", first router instance will not return FooPage but AboutPage. So The stack have to check the _context key object if he is has a subrouter.


  • reorganize route Object :
    (add "_" on internal properties)
type TRoute = {
  path: string | { [x: string]: string };
  component: React.ComponentType<any>;
  base: string;
  name: string;
  parser: Match;
  props: TRouteProps;
  children: TRoute[];
  url: string;
  getStaticProps: (props: TRouteProps) => Promise<any>;
  _fullUrl: string; // full URL who not depend of current instance
  _fullPath: string; // full Path /base/:lang/foo/second-foo
  _langPath: { [x: string]: string } | null;
  _context: TRoute;
};

v3.0.0-beta.6

12 Jul 15:47
Compare
Choose a tag to compare

About

In order to use this router on server side, we need to be able to request API on the server side too. In this case, request will be print as javascript window object on the renderToString html server response. The client will got this response.

Strategy

  • Add getStaticProps() property to each route object.
const routes = [
 {
    path: "/",
    component: HomePage,
    name: "Home",
    getStaticProps: async (props) => {
      const res = await fetch("https://api.com/home");
      const api = await res.json();
      return { api };
    },
  },
  {
    path: "/article/:slug",
    component: ArticlePage,
    name: "Article",
    getStaticProps: async (props) => {
      // props contains route props and params (ex: slug: "article-1")
      const res = await fetch(`https://api.com/posts/${props.params.slug}`);
      const api = await res.json();
      return { api };
    },
  },
]
  • Then, get response data populated in page component props
function HomePage({ api }) {
 return <div>{ api.title }</div>
}

Changes

TRoute name is required on getStaticProps usage.

TODO

  • add getStaticProps to TRoute to allows api fetching on the server side (@willybrauner)
  • fetch getStaticProps promise in Router, and associate response ton newRoute.props (@willybrauner)
  • expose props (included params as getStaticProps Arg (@willybrauner)

// add staticLocation as instance param
  const langService = new LangService({
    staticLocation: url,
    languages,
  });
  • Hash url check on server-side (@Hlefrant)
  • index-server.tsx -> create global function requestStaticPropsFromRoute (@willybrauner)
    Request static props of current route on server side
    const ssrStaticProps: { props: any , name: string } = await requestStaticPropsFromRoute({
      url: "/hello",
      base: "/",
      routes: routesList,
      langService: langService,
    });
  • update repo struct: put examples as independents workspaces in examples folder / install pnpm for managin workspaces.

Now install dependencies with

pnpm i
# run client example 
npm run example-client:dev
# run ssr example 
npm run example-ssr:dev 
  • fix circular dependencies on build
  • write documentation (@willybrauner)

cher-ami router v2 is out 🎉

13 Apr 09:16
Compare
Choose a tag to compare

Structure improvement

  • (Replace eventEmitter by React context #65)
    Using react context API, it's one less external tool to use in the project.

  • (Internalize router manager in router #79)
    There is no more RouterManager class instantiated in Router.tsx. All the route changing listening is made inside Router.tsx who call external matcher helper function. It's now easier to test this matcher function an avoids having to communicate between the independent Router instance and the react Router component.

  • (Update debug and dev server dependencies #74)
    Replace debug lib by the smaller lib @wbe/debug

Features

  • (Replace path-parser by path-to-regexp #75)
    path-to-regexp allows optional params witch was not possible with path-parser. route path can be now defined as /path/:id?. /path and /path/hello will match.

  • (Infinit sub Routers #79)
    It was working on one level, it's now possible to nest as many routers as needed.

  • new helpercreateUrl()
    Create a formated URL by string, or TOpenRouteParams. This method is available everywhere inside component.

import { createUrl } from "@cher-ami/router"
createUrl({ name: "ArticlePage", params: {id: "foo"} }) // will return a path as `/articles/foo` (depend of route paths)
  • new helper openRoute()
    Push new route in current history. Stack(s) component(s) will return the appropriate route. This method is available everywhere inside component.
import { openRoute } from "@cher-ami/router"
openRoute({ name: "ArticlePage", params: {id: "foo"} }) // stack will render the appropriate component
  • (add Setlang hook #81)
    If LangService instance is set to the Router, we can use this hook to quickly get current language and change language.
const [lang, setLang] = useLang();
  • (extended Link props with anchor html attributes #78)

  • (Export type #83)
    Expose TRoute type

  • (Expose routers #76)
    Global Routers object is now exposed. It contains informations available for all routers instances.

  • (Use microbundle instead of TSC #77 #82)

  • Add getPaused() and setPaused() as router instance properties, allows to paused handleHistory internal method. It's pretty usefull to prevent some bugs during routes transitions. #89

const { getPaused, setPaused } = useRouter()

// paused handle history 
setPaused(true)

// get current paused state
const isPaused = getPaused() // true
  • Update all dependencies. Move to React 18. #92

breaking changes

useRouter

  • useRoute() as been removed. currentRoute & previousRoute are now available from useRouter()
// before
const { currentRoute, previousRoute } = useRoute();
// v2
const { currentRoute, previousRoute } = useRouter();

LangService

  • LangService is now set to the router as an independent instance.
// before - it was a singleton
LangService.init([{ key: "en" }, { key: "fr" }], true, "/")
// ...
<Router middlewares={langMiddleware}> ... </Router>

// v2 - independent instance
const langService = new LangService({
  languages: [{ key: "en" }, { key: "fr" }],
  showDefaultLangInUrl: true,
  base: "/",
});
// ...
<Router langService={langService}> ... </Router>

Sub Router

Sub Router declaration is not as automatic and"magical" as before. In v2, we need to define manually what is the sub router base & routes.
getPathByRouteName, getSubRoutersBase & getSubRoutersRoutes are available to help use.

// before - we deduced internally what was sub routes and full base URL.
<Router base={"/foo"}>
</Router>

// v2

  // Get parent router context
  const { base, routes } = useRouter();

  // Parsed routes list and get path by route name
  const path = getPathByRouteName(routesList, "FooPage"); // "/foo"
  // ...
  return (
    <div>
      <Router
        // -> "/base/:lang/foo" (if last param is false, ':lang' will be not added)
        base={getSubRoutersBase(path, base, true)}
        // children routes array of FooPage
        routes={getSubRoutersRoutes(path, routes)}
      >
        <Stack />
      </Router>
    </div>
  );

useHistory

useHistory hook is returning now the whole of history object instead of an array of history location.

const history = useHistory(()=> {
}, [])
//...
history.push() // etc...

Fix

Fix #86 bug when a route is defined after a route witch contains subroutes.

{
    path: "/",
    component: HomePage,
  },
  {
    path: "/about",
    component: AboutPage,
    children: [
      {
        path: "/foo",
        component: FooPage,
      },
      {
        path: "/bar",
        component: BarPage,
       }
    ],
  },
 // this last route is now properly rendered
 {
    path: "/last",
    component: LastPage,
  },
}

v2.0.0-beta-11

12 Apr 08:19
Compare
Choose a tag to compare

feature

  • #92 Update all dependencies. Move to React 18.

v2.0.0-beta-10

10 Feb 10:13
Compare
Choose a tag to compare

Feature

Add getPaused() and setPaused() as router instance properties, allows to paused handleHistory internal method. It's pretty usefull to prevent some bugs during routes transitions.

const { getPaused, setPaused } = useRouter()

// paused handle history 
setPaused(true)

// get current paused state
const isPaused = getPaused() // true