Skip to content

Instantly share code, notes, and snippets.

@koistya
Last active September 1, 2016 13:39
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save koistya/607326694e1c8ee49c6e8515a1eb1fc3 to your computer and use it in GitHub Desktop.
Save koistya/607326694e1c8ee49c6e8515a1eb1fc3 to your computer and use it in GitHub Desktop.
Declarative routes with React and Relay

Custom Routing solution with React and Relay

Start by checking You might not need React Router article by Konstantin Tarkus

It shows how to implement Router.resolve(routes, context) method with just a few lines of code, which in combination with history npm module (handles client-side navigation) can be a good routing solution for many React applications.

This article demonstrates imperative (functional) routes. But if you're planning to use Relay, it might be a better idea to use declarative routes instead. For example, you can declare all routes for your React application in a single routes.json file that may look like this:

routes.json

[
  {
    "path": "/tasks",
    "component": "./views/tasks/TodoList",
    "queries": {
      "viewer": "query { viewer }",
      "tasks": "query { tasks }"
    }
  },
  {
    "path": "/tasks/:id(\\d+)",
    "component": "./views/tasks/TodoItem",
    "queries": {
      "viewer": "query { viewer }",
      "tasks": "query { task(id: $id) }"
    }
  }
]

And convert that into JavaScript array at compile time by using routes loader for Webpack. So, after this JSON code is transformed by routes-loader, it may look as follows:

import Relay, { RelayRenderer } from 'react-relay';

module.exports = [
  {
    path: "/tasks",
    queries: {
      viewer: () => Relay.QL`query { viewer }`,
      tasks: () => Relay.QL`query { tasks }`
    },
    async action({ params }) {
      const [Component, props ] = await Promise.all([
        System.import('./views/tasks/TodoItem')},
        Object.keys(this.queries).map(x => this.queries[key]).map(...) // TODO: Fetch data
      ]);
      return <Component {...props} />;
    }
  },
  {
    path: '/tasks/:id(\\d+)',
    queries: {
      viewer: () => Relay.QL`query { viewer }`,
      tasks: () => Relay.QL`query { task(id: $id) }`
    },
    async action({ params }) {
      const [Component, ...data ] = await Promise.all([
        System.import('./views/tasks/TodoItem')},
        ...Object.keys(this.queries).map(key => new Promise(resolve => {
          const query = Relay.createQuery(this.queries[key], params);
          Relay.Store.primeCache({ query: query }, readyState => {
            if (readyState.done) {
              resolve(Relay.Store.readQuery(query)[0])
            }
          });
        }));
        Object.keys(this.queries).map(x => this.queries[key]).map(...) // TODO: Fetch data
      ]);
      return <Component {...Object.keys(this.queries).reduce((o, k, i) => { o[k] = data[i]; }, {})} />;
    }
  }
];

Note: This is just a rough example, needs to be cleand up / refactored for production use.

Then you can use these routes as usual:

import Router from 'universal-router'; // or, a custom resolve() functions
import routes from './routes.json'; // Transformed with utils/routes-loader.js

Router.resolve(routes, { path: '/tasks/123' }).then(component => ReactDOM.render(component, container));

You can find an example of this routing approach in React Static Boilerplate project. That one uses whatwg-fetch for data fetching, but it can be easily replaced with Relay.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment