Skip to content

oneut/async-react-router

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

async-react-router

Build Status npm version Coverage Status dependencies Status Commitizen friendly

Async-react-router is react router that can easily get initial props using async/await or promise.
If you use this library, You can get the initial props like Next.js.
And this library works only on client also.

Version

Version React RxJS README
2.0 15.X or 16.X 6.X Link
1.0 15.X or 16.X 5.X Link

In order to correspond to dynamic import, v2 has breaking change from v1.

Features

  • Support getInitialProps() like Next.js.
  • Support only on client-side.
  • Support sever-side rendering.
  • Support URL parameters.
  • Support history package. The following history type is supported.
    • Hash history
    • Browser history
    • Memory history
  • Support dynamic import.
  • No depend on react-router.

Demo

Installation

Async-react-router has peer dependencies of rxjs@6.x.x which will have to be installed.

npm install async-react-router react react-dom rxjs --save

Example

import React from 'react';
import { render } from "react-dom";
import { createRouter } from "async-react-router";

function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

class Home extends React.Component {
    static initialPropsWillGet(attributes, prevAttributes) {
        console.log('Taking a break...');
    }
    
    static async getInitialProps(attributes, prevAttributes) {
        await sleep(5000);
        return {
            message: 'Home is five second sleep.'
        };
    }
    
    static initialPropsDidGet(props, prevProps) {
        console.log('Five second later');
    }

    render() {
        return (
            <div>
                <h2>Home</h2>
                <dl>
                    <dt>Message</dt>
                    <dd>{this.props.message}</dd>
                </dl>
                <ul>
                    <li><Link to="/page">Page Index</Link></li>
                    <li><Link to="/page/1">Page 1</Link></li>
                </ul>
            </div>
        );
    };
}

const router = createRouter();
router.route("/", Home);
router.run((Root) => {
  render(<Root/>, document.getElementById("app"));
});

API

createRouter()

createRouter() generates router instance. Default history type is Hash history.

import { createRouter } from "async-react-router";

const router = createRouter();

If you want to change history type to browser history or memory history, you can define below.

// Browser history
import { createRouter, createBrowserHistory } from "async-react-router";
const router = createRouter(createBrowserHistory());

// Memory history
import { createRouter, createMemoryHistory } from "async-react-router";
const router = createRouter(createMemoryHistory());

router.route(path, component, name)

Route links a path and a component.

  • path - Any valid URL path that path-to-regexp understands. Required.
  • component - A react component to render only when the location matches. Required.
  • name - Route name. You can use at Request.name(name), or URL.name(name). Optional.
import React from 'react';
import { createRouter } from 'async-react-router';
import Home from "./components/Home";

const router = createRouter();
router.route("/", Home, "Home");
router.run((Root) => {
  render(<Root/>, document.getElementById("app"));
});

router.asyncRoute(path, () => promise, name)

If you want to use dynamic import, You can define using asyncRoute().

  • path - Any valid URL path that path-to-regexp understands. Required.
  • component - A react component to render only when the location matches. Required.
  • name - Route name. You can use Request.name(name), or URL.name(name). Optional.
import { createRouter } from 'async-react-router';

const router = createRouter();
router.asyncRoute("/", () => import("./components/Home"), "Home");
router.run((Root) => {
  hydrate(<Root />, document.getElementById("app"));
});

router.run((RootComponent) => void)

run generates the root component.

import { createRouter } from 'async-react-router';
import Home from "./components/Home";

const router = createRouter();
router.route("/", Home, "Home");
router.run((Root) => {
  render(<Root />, document.getElementById("app"));
});

If you want to add parameters, you can define parameters with the root component parameters.

import { createRouter } from 'async-react-router';
import Home from "./components/Home";

const router = createRouter();
router.route("/", Home, "Home");
router.run((Root) => {
  render(<Root any={"any"}/>, document.getElementById("app"));
});

router.setFirstComponent(component)

If you want to render any component at first rendering, you can define component.

import { createRouter } from 'async-react-router';
function FirstComponent() {
  return (
    <div className="text-center" style={{margin: "100px 0"}}>
      <i className="fa fa-cog fa-spin fa-5x fa-fw"/>
    </div>
  )
}

const router = createRouter();

// Set first rendered component.
router.setFirstComponent(FirstComponent);

router.run((Root) => {
  hydrate(<Root />, document.getElementById("app"));
});

router.setInitialProps(parameters)

If you want to render server-side, you will want to give initial data. In that case, you can set initial data with setInitialProps().
When you use setInitialProps(), initialPropsWillGet() and initialPropsDidGet() are not called for the first time.
Only getInitialProps() is called.

import { createRouter } from 'async-react-router';

const router = createRouter();

// Set data from server.
router.setInitialProps(
  JSON.parse(document.getElementById("initial-props").innerText);
);

router.run((Root) => {
  hydrate(<Root/>, document.getElementById("app"));
});

Link

<Link> makes a request event and renders component matching route path.

import { Link } from 'async-react-router';

<Link to="/">Home</Link>

Route component

component.initialPropsWillGet(attributes, prevAttributes): void

Route component can have initialPropsWillGet().
initialPropsWillGet() is invoked immediately before mounting occurs. It is called before getInitialProps() This method is static.

initialPropsWillGet() has arguments below.

  • attributes - Current route attributes.
    • pathname - String of the current path.
    • params - Object with the parsed url parameter. Defaults to {}.
  • prevAttributes - Previous route attributes. Defaults to {}.
    • pathname - String of the previous path.
    • params - Object with the parsed url parameter at previous page. Defaults to {}.

async/await is not supported.

component.getInitialProps(attributes, prevAttributes): Object

Route component can have getInitialProps() that can use async/await.
getInitialProps() perform the rendering after promise has been resolved, The resolved data can be retrieved as props of component. This method is static.

And getInitialProps() has arguments below.

  • attributes - Current route attributes.
    • pathname - String of the current path.
    • params - Object with the parsed url parameter. Defaults to {}.
  • prevAttributes - Previous route attributes. Defaults to {}.
    • pathname - String of the previous path.
    • params - Object with the parsed url parameter at previous page. Defaults to {}.
import { createRouter } from 'async-react-router';

class User extends React.Component {
    static async getInitialProps(attributes, prevAttributes) {
        console.log(attributes.params.userId);
        return { 
          data: "Get initial props!!" 
        };
    }
    
    render() {
        return (
            <div>
                <div>UserId: {this.props.params.userId}</div>
                <div>Data: {this.props.data}</div>
            </div>
        );
    }
}

const router = createrRouter();
router.route("/user/:userId", User);
router.run((Root) => {
  render(<Root/>, document.getElementById("app"));
});

component.initialPropsDidGet(props, prevProps): void

Route component can have initialPropsDidGet().
initialPropsDidGet() is called after getInitialProps().
If more than one promise is pending, Async-react-router gets only data of last executed promise.
For this reason, initialPropsDidGet() is executed only when the last promise is resolved. This method is static.

initialPropsDidGet() has arguments.

  • props - Current props of components defined at route.
    • pathname - String of the current path.
    • params - Object with the parsed url parameter. Defaults to {}.
    • {data} - Data retrieved using getInitialProps().
  • prevProps - Previous props of components defined at route. First rendering to {}.
    • pathname - String of the previous path.
    • params - Object with the parsed url parameter at previous page. Defaults to {}.
    • {data} - Data retrieved using getInitialProps().

async/await is not supported.

Request

Request.to(path)

When you want to push next request, you can use to of Request.

  • path - String of next path.
import { Request } from 'async-react-router';

Request.to('/next'); // Change url to `#/next`.

Request.name(routeName, urlParameters)

You can make next request from the name defined at route.

  • routeName - Route name for next request.
  • urlParameters - Object of next url parameters. Optional.
import React from 'react';
import { render } from 'react-dom';
import { createRouter, Request } from 'async-react-router';

class User extends React.Component {
    render() { return (<div>{this.props.params.userId}</div>); };
}

const router = createRouter();
router.route("/user/:userId", User, "User");
router.run((Root) => {
  render(<Root/>, document.getElementById("app"));
});

Request.name("User", {userId: 1}); // Change url to `#/user/1`.

Request.isActive(path)

When you want to check path, you can use isActive() of Request.

import { Request } from 'async-react-router';

// When current path is `/`...
Request.isActive('/');     // true
Request.isActive('/path'); // false

URL

URL.to(path)

When you want to make path, you can use to of URL.

  • path - String of path.
import { URL } from 'async-react-router';

URL.to('/next'); // String `#/next`.

URL.name(routeName, urlParameters)

You can make URL from the name defined at route.

  • routeName - Route name.
  • urlParameters - Object of url parameter, if it requires.
import React from 'react';
import { render } from 'react-dom';
import { createRouter, URL } from 'async-react-router';

class User extends React.Component {
    render() { return (<div>{this.props.params.userId}</div>); };
}

const router = createRouter();
router.route("/user/:userId", User, "User");
router.run((Root) => {
  render(<Root/>, document.getElementById("app"));
});

URL.name("User", {userId: 1}); // String `#/user/1`.

Server Side Rendering

Async-react-router supports server-side rendering.

  • SSR.createServerRouter() generates server-side router instance.
  • You can deal with SSR just by changing createRouter() to SSR.createRouter() on client side.
  • It is also possible to obtain resolved data on the server side via HTML on client-side.

Server Side

SSR.createServerRouter()

SSR.createServerRouter() generates server-side router instance. Supported history type is only memory history. serverRouter instance has route() and asyncRoute() also.

import { SSR } from "async-react-router";

app.get("*", function(req, res) {
  function setRoutes(router) {
    router.route("/", IndexPage);
    router.asyncRoute("/user", () => import("./UserPage"));
  }
  
  const serverRouter = SSR.createServerRouter();
  setRoutes(serverRouter);
}

serverRouter.runUsingPathname(pathname, callback(RootComponent, data) => void)

serverRouter.runUsingPathname() generates root component and initial data.
getInitialProps() and initialPropsWillGet(), initialPropsDidGet() are not called for the first time.

import ejs from "ejs";
import React from "react";
import ReactDOMServer from "react-dom/server";
import express from "express";
import { SSR } from "async-react-router";
import fs from "fs";

const app = express();

app.get("*", function(req, res) {
  // Please make another file and import.
  function setRoutes(router) {
    router.route("/", IndexPage);
    router.asyncRoute("/user", () => import("./UserPage"));
  }
  
  const serverRouter = SSR.createServerRouter();
  setRoutes(serverRouter);
  serverRouter.runUsingPathname(req.url, (Root, data) => {
    fs.readFile("index.html", function(err, result) {
          const compiled = ejs.compile(result.toString("utf8"), "utf8");
          const html = compiled({
            component: ReactDOMServer.renderToString(<Root/>),
            data: data
          });
    
          res.write(html);
          res.end();
        });
  });
}

Client Side

SSR.createRouter()

SSR.createrRouter () generates a router instance with the same functionality as createrRouter (). The only difference is history type.
Supported history type is only browser history. Hash History and Memory History cannot be used.

import React from "react";
import { hydrate } from "react-dom";
import { SSR } from "async-react-router";

// Please make another file and import.
function setRoutes(router) {
    router.route("/", IndexPage);
    router.asyncRoute("/user", () => import("./UserPage"));
}

const router = SSR.createRouter();
setRoutes(router);
router.setInitialProps(JSON.parse(document.getElementById("initial-props").innerText));
router.run((Root) => {
  hydrate(<Root/>, document.getElementById("app"));
});

This is the same process as below.

import React from "react";
import { hydrate } from "react-dom";
import { createRouter, createBrowserHistory } from "async-react-router";

// Please make another file and import.
function setRoutes(router) {
    router.route("/", IndexPage);
    router.asyncRoute("/user", () => import("./UserPage"));
}

const router = createRouter(createBrowserHistory());
setRoutes(router);
router.setInitialProps(JSON.parse(document.getElementById("initial-props").innerText));
router.run((Root) => {
  hydrate(<Root/>, document.getElementById("app"));
});

Thanks for the inspiration

License

MIT