react-router-dispatcher is designed to work with react-router v4.x, it:
- uses actions to encapsulate behaviors that can be invoked before rendering
- supports server-side rendering, including resolving async promises before rendering
- requires using react-router-config v4.x route configuration
You can find it on the V1 branch. Version 2+ has been simplified and no longer requires redux
// npm
npm install --save react-router-dispatcher
// yarn
yarn add react-router-dispatcher
- react-router-dispatcher-status-code set HTTP status code of streaming responses
- react-router-dispatcher-redirect redirect routes that support SSR streams by redirecting before render
- react-router-dispatcher-metadata SSR stream supported HTML metadata
- react-router-dispatcher-chunk react-chunk dynamic import support to support code-splitting
If your building a universal application, use the createRouteDispatchers
factory method.
// dispatcher.js
import { createRouteDispatchers } from 'react-router-dispatcher';
import { LOAD_METADATA } from 'react-router-metadata-action';
import { LOAD_DATA } from './loadDataAction';
// === route dispatcher configuration ===
// 1. define react-router-config route configuration
const routes = [...];
// 2. define the ORDER that actions are invoked
const orderedActionNames = [[LOAD_DATA], [LOAD_METADATA]];
// Use the createRouteDispatchers factory,
// it returns everything required for rendering dispatcher actions
const {
UniversalRouteDispatcher,
ClientRouteDispatcher,
dispatchClientActions,
dispatchServerActions
} = createRouteDispatchers(routes, orderedActionNames /*, options */);
import Html from 'react-html-metadata';
import { dispatchServerActions, UniversalRouteDispatcher } from './dispatcher';
import apiClient from './someOtherPackage';
const location = request.url; // current request URL, from expressjs or similar
const actionParams = { apiClient }; // passed to all dispatch action methods
dispatchServerActions(location, actionParams /*, options */).then(({ metadata, store }) => {
const staticRouterCtx = {};
// Render the response, supports rendering to stream and string
const stream = renderToNodeStream(
<Html metadata={metadata}>
<StaticRouter location={location} context={staticRouterCtx}>
<UniversalRouteDispatcher appData={store} />
</StaticRouter>
</Html>);
res.write("<!DOCTYPE html>");
stream.pipe(res);
});
import { hydrate, render } from 'react-dom';
import Html from 'react-html-metadata';
import {
dispatchClientActions,
UniversalRouteDispatcher,
ClientRouteDispatcher
} from './dispatcher';
const location = window.location.pathname; // current url, from browser window
const appData = window.__AppData; // data serialized from the server render
// This is synchronous
// It uses the appData to recreate the metadata on the client
const { metadata } = dispatchClientActions(location, appData);
// Use hydrate() with server-side rendering,
// otherwise use render() with <ClientRouteDispatcher />
hydrate(
<Html metadata={metadata}>
<BrowserRouter>
<UniversalRouteDispatcher />
</BrowserRouter>
</Html>
);
For the client app, use the exported <RouteDispatcher>
component to render your application.
import { RouterDispatcher } from 'react-router-dispatcher';
const routeCfg = []; // same as server (react-router-config routes)
// render your app
<Router ...>
<RouterDispatcher routes={routeCfg} actionNames={[['loadData']]} />
</Router>
You must assign actions to route components (components that are assigned directly to react-router-config style routes)
Packages that support react-router-dispatcher should export actions.
// loadDataAction.js - a simple action for loading async data
import getDisplayName from 'react-display-name';
export const LOAD_DATA = 'LOAD_DATA_ACTION';
export default function loadDataAction() {
return {
name: LOAD_DATA,
staticMethodName: 'loadData',
initServerAction: (params) => ({
store: params.store || {}
}),
filterParamsToProps: (params) => {
store: params.store
}
};
}
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withActions } from 'react-router-dispatcher';
import loadDataAction from './loadDataAction';
class ExampleComponent extends Component {
static propTypes = {
store: PropTypes.object.isRequired,
apiClient: PropTypes.object.isRequired
};
// loadDataAction invokes this method to load data from an api
static loadData(actionProps, routerCtx) {
const {
location,
match: {
params
},
store,
apiClient
} = actionProps;
// async functions must return a Promise
return apiClient.loadById(params.id).then((data) => {
store.exampleData = data;
});
}
render() {
const {store: { exampleData }} = this.props;
return <div>{exampleData}</div>
}
}
// the mapper must return the 'propTypes' expected by the component
const mapParamsToProps = ({ apiClient }) => { apiClient };
export default withActions(mapParamsToProps, loadDataAction())(ExampleComponent);
It's recommended that all actions are defined as factory functions that return new action instances. It can be useful to allow actions to accept parameters to customize the actions behavior.
name: string
- required
- The action name should also be exported as a
string
, to be used for configuring action order
staticMethod: (props, routerCtx) => any
- One of
staticMethod
orstaticMethodName
is required - Action method implementation, can be defined here or using static methods on components actions are assigned to
- return a
Promise
for async actions - for non-async actions, return data
staticMethodName: string
- One of
staticMethod
orstaticMethodName
is required - the name of the static method required on any
Component
that the action is applied to
filterParamsToProps: (params) => Object
- required
- filters all
actionParams
to include on params required by this action
hoc: (Component, ActionHOC) => node
- Optional
- Defines a higher-order component that is applied to all components that have the action assigned
- Using higher-order components makes actions very versatile!
initServerAction: (actionParams) => Object
- Optional, but required if the action supports being invoked on the server before rendering
- if your action supports server-side usage but does not need to perform any init, return an empty object
initServerAction: (params) => {}
initClientAction: (actionParams) => Object
- Optional, but required if the action supports being invoked on the client before rendering
- if your action supports client-side usage but does not need to perform any init, return an empty object
initClientAction: (params) => {}
successHandler: (props, routerCtx) => void
- Optional, invoked after this action is successfully invoked on each matching route
- Params will include any value(s) assigned from the static action methods
- NOTE:
params
are the raw dispatcher parameters
errorHandler: (err, props) => void
- Optional, invoked if any static action methods or success handler fails
stopServerActions: (props, routerCtx) => boolean
- Optional, allows an action to short-circuit/prevent invocation of following action sets with
dispatchOnServer()
- For example; An action may determine a redirect is required, therefore invoking following action sets is a waste of resources
routes: Array
- Routes defined using the react-router-config format.
orderedActionNames: string | Array<string> | Array<Array<string>> | (location, actionParams) => string|Array<string>|Array<Array<string>>
- Configures the order that actions will be executed
- A
string
can be used if only 1 action is used - An array of action names will execute all actions in parallel
- A nested array enables actions to be executed serially
- ie:
[['loadData'], ['parseData']]
firstloadData
is invoked on each component, thenparseData
is invoked on each component
- ie:
- A function,
dispatchActions(location, actionParams)
. Return one of the previously defined types (string, array, nested array).
options: Object
- routeComponentPropNames:
Array<string>
, route prop name(s) that are known to be react components - loadingIndicator:
React Component
, a component to display for client-side renders when loading async data
A higher-order component function for assigned actions to components
mapParamsToProps: (params, routerCtx) => Object
- A function that maps action parameters to prop values required by any actions applied to the component.
- Pass
null
if no mapping function is required by the component.
actions:
- one or more actions to be applied to a react component
- separate multiple actions using a comma:
withActions(null, loadData(), parseData())(Component)
Props:
routes: Array
- Routes defined using the react-router-config format.
actionNames: string | Array<string> | Array<Array<string>> | (location, actionParams) => string|Array<string>|Array<Array<string>>
- Configure the action(s) defined any any route component to invoke before rendering.
- See createRouteDispatchers.orderedActionNames for more information
routeComponentPropNames: Array<string>
- The prop names of route components that are known to be react components
- The default value is
component
.
actionParams: any
- Any value can be assigned to the action params, the value is passed to all action methods, common usages include passing api clients and application state (such as a redux store)
loadingIndicator: React Component
- A custom component to display on the client when async actions are pending completion
- note: this is only rendered on the client
render: (routes, routeProps) => node
- A custom render method
- you must invoke the react-router
renderRoutes
method within the render method
The defineRoutes
utility method automatically assigns keys
to routes that don't have a key manually assigned.
This key can be accessed from actions to determine the exact route that is responsible for invoking the action.
import { defineRoutes } from 'react-router-dispatcher';
const routes = defineRoutes([
// define react-router-config routes here
]);
Resolves all route components for a requested location and a given set of routes.
import { matchRouteComponents } from 'react-router-dispatcher';
const matchedRoutes = matchRouteComponents(location, routes, routeComponentPropNames);
const [component, match, routerContext] = matchedRoutes[0];
const { route, routeComponentKey } = routerContext;
For questions or issues, please open an issue, and you're welcome to submit a PR for bug fixes and feature requests.
Before submitting a PR, ensure you run npm test
to verify that your coe adheres to the configured lint rules and passes all tests. Be sure to include unit tests for any code changes or additions.
MIT