Imagine your app has a multi-step wizard such as a shopping cart checkout with a variety of error and special case screens. It is very convenient to have your observable store's business logic decide what state it should be in and to have the UI render the appropriate screen.
At the same time, you still need to allow the user to navigate back or between screens manually, so the observable state must not force a particular screen to be displayed.
To accomplish this with mobx and react-router 3.x you can use an autorun reaction in a parent component that forces a re-route only when the observable changes but not when the user navigates between child components.
In MobX 3.x/Mobx-React 4.x, there is a bit of a trick to doing this correctly, so below is an example that re-routes based on the value of some store.stage observable.
WizardRouter.jsx:
import React from'react';import { Router, Route, hashHistory} from'react-router';import store from'./store';import Wizard from'./Wizard';// some screens:import Start from'./Start';import Error from'./Error';import Select from'./Select';classWizardRouterextendsReact.Component {// Determines which screen to show on initial loadredirectToDefault= (nextState, replace) => {if (nextState.location.pathname ==='/wizard') {replace(this.selectRoute() ||'/wizard/start'); } };// This method is also provided as selectRoute handler to autorun in Wizard.jsxselectRoute= () => {switch (store.stage) {casestore.STAGES.error:return'/wizard/error';casestore.STAGES.select:return'/wizard/select'; }returnnull; };render() {return ( <Routerhistory={hashHistory}> <Routepath="/wizard"component={Wizard}onEnter={this.redirectToDefault}selectRoute={this.selectRoute} > <Routepath="start"component={Start} /> <Routepath="error"component={Error} /> <Routepath="select"component={Select} /> </Route> </Router> ); }}exportdefault WizardRouter;
Wizard.jsx:
import React from'react';import { routerShape } from'react-router/lib/PropTypes';import { autorun, action } from'mobx';import { observer } from'mobx-react';import store from'./store';let autorunDisposer;@observer classWizardextendsReact.Component {componentWillMount() {/* In MobX 3.x/Mobx-React 4.x, @observer components' props are made observable, so referencing this.props.route.selectRoute inside the autorun would cause it to fire not only when store.stage changes but when location changes, thus making user navigation between screens impossible. So, dereference early to make it NOT observable. */constselectRoute=this.props.route.selectRoute; autorunDisposer =autorun(() => {constpath=selectRoute();// any observable accessed inside the action will// not cause this autorun to re-run if it changesaction(() => {if (path &&!this.props.router.isActive(path)) {this.props.router.push(path); } })(); }); }componentWillUnmount() { autorunDisposer &&autorunDisposer(); }render() {return ( <div> {this.props.children} </div> ); }}Wizard.propTypes = { children:React.PropTypes.element, router: routerShape,// React-Router provides this automatically// props provided to Route component are available here: route:React.PropTypes.shape({ selectRoute:React.PropTypes.func, }),};exportdefault Wizard;