mobx-react-router: Using Browser buttons, gets rendered page out of sync with path

My routing seems to work fine. But when I use the browser ‘back’ button, the URL changes correctly, but the page then doesn’t match the URL. The first time I hit the back button, nothing happens. Then second time I click it goes to where it should have gone the first time.

This is my entry point: index.tsx:

 {UserStore} from './data/stores/UserStore';
declare let module: any;

import * as React from "react";
import * as ReactDOM from "react-dom";
import {AppContainer} from 'react-hot-loader';
import { Router } from 'react-router';
// import createHistory from 'history/createBrowserHistory';
import createHistory from 'history/createHashHistory';
import { Provider } from 'mobx-react';
import { RouterStore, syncHistoryWithStore } from 'mobx-react-router';
import { App } from "./components/app/App";

const history = createHistory();
const routingStore = new RouterStore();
const users = new UserStore();
const synchronizedHistory = syncHistoryWithStore(history, routingStore);
const stores = {
    routing: routingStore,
    users: users
};

const renderApp = (Component: any) =>
{
    ReactDOM.render(
        <AppContainer>
            <Provider {...stores}>
                <Router history={synchronizedHistory}>
                    <Component />
                </Router>
            </Provider>
        </AppContainer>,
        document.getElementById("app_content")
    );
};

renderApp(App);

// Hot Module Replacement API
if (module.hot) {
    module.hot.accept('./components/app/App', () => {
        renderApp(App)
    });
}

My app file:

import 'bootstrap';
import * as React from 'react';
import {TopNav} from '../topnav/TopNav';
import {History} from 'history';
import {Routes} from './Routes';
import {inject, observer} from 'mobx-react';
import {UserStore} from '../../data/stores/UserStore';
import {Level, Logger} from '../../util/Logger';

interface IAppProps { routing: History, users: UserStore }

@inject('routing')
@observer
export class App extends React.Component<IAppProps, undefined>
{
    //noinspection JSMethodCanBeStatic,JSUnusedLocalSymbols
    private log(msg: string, level?: Level): void
    {
        Logger.get().log(msg, 'App', level || Level.TRACE);
    }

    render(): JSX.Element
    {
        const { location } = this.props.routing;
        return (
            <div>
                <TopNav/>
                <div className='container body-content'>
                    <Routes/>
                    <div className='small'>path: {location.pathname}</div>
                </div>
            </div>);
    }
}

My routes file:

import * as React from 'react';
import {Route, Switch} from 'react-router-dom';
import {HomePage, PageOne, PageThree, PageTwo} from '../home/HomePage';
import {PendingDeliveryPage} from '../delivery/PendingDeliveryPage';
import {DeliveryExecutePage} from '../delivery/DeliveryExecutePage';


export class Routes extends React.Component<undefined, undefined>
{
    public render(): JSX.Element
    {
        return (
            <Switch>
                <Route exact path='/' component={HomePage} />
                <Route path='/delivery/:id/execute' component={DeliveryExecutePage} />
                <Route path='/delivery' component={PendingDeliveryPage} />
                <Route path='/one' component={PageOne} />
                <Route path='/two' component={PageTwo} />
                <Route path='/three' component={PageThree} />
            </Switch>
        );
    }
}

And lastely my test pages:

import * as React from "react";
import {Link, RouteComponentProps} from 'react-router-dom';

export class HomePage extends React.Component<RouteComponentProps<any>, undefined>
{
    public render(): JSX.Element
    {
        return (
            <div>
                <h1>Home</h1>
                <ul>
                    <li><Link to="/">Home</Link></li>
                    <li><Link to="/one">One</Link></li>
                    <li><Link to="/two">Two</Link></li>
                    <li><Link to="/three">Three</Link></li>
                </ul>
            </div>);
    }
}

export class PageOne extends React.Component<RouteComponentProps<any>, undefined>
{
    public render(): JSX.Element
    {
        return (
            <div>
                <h1>Page One</h1>
                <ul>
                    <li><Link to="/">Home</Link></li>
                    <li><Link to="/one">One</Link></li>
                    <li><Link to="/two">Two</Link></li>
                    <li><Link to="/three">Three</Link></li>
                </ul>
            </div>);
    }
}

export class PageTwo extends React.Component<RouteComponentProps<any>, undefined>
{
    public render(): JSX.Element
    {
        return (
            <div>
                <h1>Page Two</h1>
                <ul>
                    <li><Link to="/">Home</Link></li>
                    <li><Link to="/one">One</Link></li>
                    <li><Link to="/two">Two</Link></li>
                    <li><Link to="/three">Three</Link></li>
                </ul>
            </div>);
    }
}

export class PageThree extends React.Component<RouteComponentProps<any>, undefined>
{
    public render(): JSX.Element
    {
        return (
            <div>
                <h1>Page Three</h1>
                <ul>
                    <li><Link to="/">Home</Link></li>
                    <li><Link to="/one">One</Link></li>
                    <li><Link to="/two">Two</Link></li>
                    <li><Link to="/three">Three</Link></li>
                </ul>
            </div>);
    }
}

You can click

  1. Start at ‘Home’
  2. Click ‘One’
  3. Click ‘Two’
  4. Click ‘Three’, and everthing should be fine.
  5. Click Browser Back, path changes to /two, but page does not render anything new.
  6. Click Browser Back and path changes to /one, and now page /two renders.
  7. Click Browser Back and page changes to /, but page /one renders.
  8. etc.

Am i doinng something wrong here?

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 20

Most upvoted comments

Ok after more investigating it looks like a react-router problem, but not with the switch - with the Route component update. The Route component uses context to get the current location, and I think the shouldComponentUpdate of the mobx-react observer component (which wraps App in your example) is returning false. This in turn is messing up the context update.

Context is known to be a fairly broken feature, especially when pairing with MobX/Redux, which overwrite shouldComponentUpdate in places.

This needs bringing up with the react-router guys.

In terms of a workaround - wrapping App (the component which uses mobx observer) in withRouter from react-router works for me, as this will overwrite the shouldComponentUpdate from observer.

Sory about bring this discussion again, turns out that i’ve discovered a problem that i had im my configuration and this may help someone!

Once you create the History with synced Store, you don’t need the React Router <BrowserRouter> component

const History = syncHistoryWithStore(browserHistory, Stores.RouterStore);

You just initialize with <Router history={History}> and you’re ready to register and access your routes. My problem was caused by a duplicated initialization of the BrowserRouter in the startup React component:

class App extends Component {
  render() {
    return (
      <Provider {...Stores}>
         /** You only need this Router to the React Router work, and the routes of course */
        <Router history={History}>
          /** Problem was caused by this duplicated routing control */
          <BrowserRouter>
            <Routing/>
          </BrowserRouter> /** Root of my problem - duplicated routing control */
        </Router>
      </Provider>
    )
  }
}

I had <Router={history}> and also <BrowserRouter>, together only the last one was working and it was breaking the plugin functionality. After removing BrowserRouter with only <Router history={History}> solved my issue!

Was a silly mistake because once you learn how to configure V4 routing and you plug this package into your app everything works fine, but turns out that two Routing are trying to control the app state and just the last one work breaking the package functionality.

Well, at the end just removing BrowserRouter and let the plugin do his job with syncHistoryWithStore solved my issues! Thanks.

You probably need to wrap any components which render <Route /> components in the withRouter higher order component. That’s the easiest fix. Again I don’t see what can be done from this packages point of view is the issue comes from react-router using the unstable context API.

Sorry guys, but i’m currently having the same issue and can’t find a way to solve it. I need to wrap my entire app with withRouter?

I din’t understood this how to solve this problem.

Thank you! That solved it for me. I was wrapping my Routes class. Wrapping the app class fixed it.

Wrapping App in withRouter worked for me. You should probably put some console.logs in your components, so you can see what is/isn’t rendering.