downplay / jarl-react

Just Another Routing Library for React

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

JARL

Just Another Router Library for React.

The production grade "batteries included" controlled component router.

latest npm version downloads dependencies CircleCI build Cypress end-to-end tests Join the conversation on Discord

If you just want the docs: JARL demos and documentation

Why another router?

A web router simply performs a mapping between URL and state. I wanted something that did this job extremely well without getting in the way of application structure and without mixing routing logic up with the component tree. JARL's routes are defined in a top-level config and are used to generate a location object. Routing decisions can then be made in your application by making this location state available and using simple conditions. (For instance, the is no <Switch/> component: just use a switch statement instead!)

'Locations' are defined as simple serializable state objects. The route table is then a mapping between URLs and these objects. The router is a controlled component, meaning you decide how and when the location actually gets updated. This provides some key benefits:

  • The state can be stored in Redux, local state, or any other storage mechanism, and used for making any rendering decisions
  • URLs don't ever need to be used inside your application, instead Link can build its URL from state objects, making it very easy to tweak your route structure at any time
  • React Native support becomes very easy since we can drop the URLs entirely and everything still works!

Some examples of state mapping:

/about -> {page: "about"} /product/:productId -> {page: "product", productId: <string>}

All routing can be described using this simple approach.

Features

  • Controlled Router with "batteries included"
  • Object-based locations (instead of URLs)
  • Full querystring matching support
  • Resolve promises during routing and redirect if required
  • React Native support including back button and deep links
  • And much more...

Concrete Example

Add to your project:

yarn add jarl-react

Declare a routing table:

const routes = [
    // Home page route
    {
        path: "/",
        // Model the state however you want!
        // Here we decided to use a `page` property to
        // switch between pages:
        state: { page: "home" }
    },
    // Another static page
    {
        path: "/about",
        state: { page: "about" }
    },
    // A dynamic URL for viewing products
    {
        path: "/products/:productId",
        // The `productId` property be merged into state when the
        // route matches
        state: { page: "product" },
        // Nested child routes are straightforward:
        routes: [
            {
                path: "/comments",
                // All the state will be merged down the branch,
                // so we'll end up with the following:
                // { page: "product", productId: <id>, tab: "comments" }
                state: { tab: "comments" }
            }
        ]
    },
    // Dynamic (and in brackets for optional) query parameters:
    {
        path: "/search?q=:search&sort=(:sortKey)",
        state: { page: "search" }
    },
    // Finally a catch-all `*` route for any bad URLs...
    // ...including matching any query params into a 'query' hash
    {
        path: "/*:missingPath?*=:query",
        state: { page: "404" }
    }
];

Load the routes and a history implementation of your choice into your RoutingProvider:

const history = createBrowserHistory();
ReactDOM.render(
    <RoutingProvider history={history} routes={routes}>
        <App />
    </RoutingProvider>,
    rootElement
);

(See history package for createBrowserHistory and other variations)

And finally in your App component you can inject the state to perform the actual routing:

import { routing } from "jarl-react";

// All our possible state variables are made available in the App
const App = ({ page, productId, search, sortKey, tab, missingPath }) => {
    // Our routing is just one big switch statement - no component needed!
    switch (page) {
        case "home":
            return <HomePage />;
        case "about":
            return <AboutPage />;
        case "product":
            // Inside ProductPage we'll have a similar switch or some other
            // conditional to maybe render a tab...
            return <ProductPage productId={productId} tab={tab} />;
        case "search":
            return <SearchPage search={search} sortKey={sortKey} />;
        case "404":
            return <MissingPage path={missingPath} />;
    }
};

// State is actually injected into App using the `routing` higher-order component
export default routing()(App);

Wait, we missed something! How do you actually link to a page? JARL has a Link component much like other router libraries, but its unique feature is that we can actually generate URLs from exactly the same state objects as specified in our routing table. Your main menu might look like this:

import { Link } from "jarl-react";

const MainMenu = () => (
    <nav>
        <Link to={{ page: "home" }}>Home</Link>
        <Link to={{ page: "about" }}>About</Link>
        <Link to={{ page: "product", productId: 123 }}>
            Our Best Product Ever!
        </Link>
        <SearchForm />
    </nav>
);

These links will use the routing table in reverse to stringify all the correct URLs to your pages, e.g. the product link will become <a href="/product/123">.

The SearchForm component needs to handle links in a slightly different way, as it needs to programmatically navigate to the search page. It looks like this:

class SearchForm extends React.Component {
    state = { searchText: "" };

    handleChange = e => {
        // Standard controlled input state management
        this.setState({ searchText: e.target.value });
    };

    render() {
        return (
            <form>
                <input
                    type="text"
                    value={this.state.searchText}
                    onChange={this.handleChange}
                    placeholder="Enter search term"
                />
                {/* Use the function-as-child pattern of Link to dynamically construct the search location and navigate programmatically using `onClick` */}
                <Link to={{ page: "search", search: this.state.searchText }}>
                    {({ onClick }) => <button onClick={onClick}>Search</button>}
                </Link>
            </form>
        );
    }
}

export default SearchForm;

That's all the basics! Hopefully this gave a flavour of the power and simplicity of this routing system. For more complex scenarios, there is also a routing higher-order component giving access to current location as well as all the router functions (serializing URLs from location objects, calling navigate or redirect, and checking whether links are active). More advanced demos (such as data preloading, code splitting, Redux integration) will be showcased in the demo site...

Documentation

Detailed documentation, and demos with annotated code samples, can be viewed at the following address:

JARL demos and documentation

Changelog

Tests & Demos

git clone https://github.com/downplay/jarl-react
cd jarl-react
yarn
yarn bootstrap
yarn build

To run unit tests:

yarn test

To run demo:

yarn demo

To run E2E tests (using Cypress):

yarn e2e

Previous E2E test runs can be viewed on the awesome Cypress Dashboard.

Note: to run the full suite (including Native builds) it might be necessary to increase the max number of watches as follows:

sudo sysctl -w kern.maxfiles=5242880
sudo sysctl -w kern.maxfilesperproc=524288

See: expo/create-react-native-app#234

Community

We have a dedicated Discord server with CI announcements in #build: https://discord.gg/BVcQ6Z

Or, come and join the conversation at Reactiflux: https://discordapp.com/invite/KWHrBDe

Credits

Pattern matching by url-pattern: https://github.com/snd/url-pattern (MIT license)

(Currently using custom build at this fork: https://github.com/downplay/url-pattern)

Query string parsing by qs: https://github.com/ljharb/qs

Some ideas and inspiration from redux-first-router: https://github.com/faceyspacey/redux-first-router

And to some extent the Autoroute feature of Orchard CMS, which I was a contributor to many moons ago ;)

Recommended browser history abstraction, history by ReactTraining: https://github.com/ReactTraining/history

Copyright

©2017-2018 Downplay Ltd

Distributed under MIT license. See LICENSE for full details.

About

Just Another Routing Library for React

License:MIT License


Languages

Language:JavaScript 99.9%Language:Shell 0.1%Language:Dockerfile 0.0%