13

I have a React app (16.8.6) written in TypeScript that uses React Router (5.0.1) and MobX (5.9.4). The navigation works fine and data loads when it should, however, when I click the browser's Back button the URL changes but no state is updated and the page doesn't get re-rendered. I've read endless articles about this issue and about the withRouter fix, which I tried but it doesn't make a difference.

A typical use case is navigating to the summary page, selecting various things which cause new data to load and new history states to get pushed and then going back a couple of steps to where you started. Most of the history pushes occur within the summary component, which handles several routes. I have noticed that when going back from the summary page to the home page the re-rendering happens as it should.

My index.tsx

import { Provider } from 'mobx-react'
import * as React from 'react'
import * as ReactDOM from 'react-dom'

import App from './App'
import * as serviceWorker from './serviceWorker'
import * as Utils from './utils/Utils'

const rootStore = Utils.createStores()

ReactDOM.render(
    <Provider {...rootStore }>
        <App />
    </Provider>,
    document.getElementById('root') as HTMLElement
)

serviceWorker.unregister()

My app.tsx

import * as React from 'react'
import { inject, observer } from 'mobx-react'
import { Route, Router, Switch } from 'react-router'

import Home from './pages/Home/Home'
import PackageSummary from './pages/PackageSummary/PackageSummary'
import ErrorPage from './pages/ErrorPage/ErrorPage'
import { STORE_ROUTER } from './constants/Constants'
import { RouterStore } from './stores/RouterStore'

@inject(STORE_ROUTER)
@observer
class App extends React.Component {
    private routerStore = this.props[STORE_ROUTER] as RouterStore

    public render() {
        return (
            <Router history={this.routerStore.history}>
                <Switch>
                    <Route exact path="/" component={Home} />
                    <Route exact path="/summary/:packageId" component={PackageSummary} />
                    <Route exact path="/summary/:packageId/:menuName" component={PackageSummary} />
                    <Route exact path="/summary/:packageId/:menuName/:appName" component={PackageSummary} />
                    <Route component={ErrorPage} />
                </Switch>
            </Router>
        )
    }
}

export default App

My router store

import { RouterStore as BaseRouterStore, syncHistoryWithStore } from 'mobx-react-router'
import { createBrowserHistory } from 'history'

export class RouterStore extends BaseRouterStore {
    constructor() {
        super()
        this.history = syncHistoryWithStore(createBrowserHistory(), this)
    }
}

How I create the MobX stores

export const createStores = () => {
    const routerStore = new RouterStore()
    const packageListStore = new PackageListStore()
    const packageSummaryStore = new PackageSummaryStore()
    const packageUploadStore = new PackageUploadStore()

    return {
        [STORE_ROUTER]: routerStore,
        [STORE_SUPPORT_PACKAGE_LIST]: packageListStore,
        [STORE_SUPPORT_PACKAGE_SUMMARY]: packageSummaryStore,
        [STORE_SUPPORT_PACKAGE_UPLOAD]: packageUploadStore
    }
}

So my questions are:

  1. How can I get the page to load the proper data when the user goes back/forward via the browser?
  2. If the solution is being able to get MobX to observe changes to the location, how would I do that?
Fraction
  • 6,401
  • 3
  • 15
  • 34
Chris Tybur
  • 1,544
  • 1
  • 22
  • 36
  • Maybe this will be useful: https://stackoverflow.com/questions/4570093/how-to-get-notified-about-changes-of-the-history-via-history-pushstate – The Witness Jul 19 '19 at 10:03

4 Answers4

1

If I remember correctly, using a browser back function does not reload the page (I might be wrong).

Why not try to detect the back action by a browser and reload the page when detected instead?

You can try the following code to manually reload the page when the browser back button is clicked.

$(window).bind("pageshow", function() {
  // Run reload code here.
});

Also out of curiosity, why do you need so many different stores?

Norman
  • 324
  • 2
  • 10
1

React router monitors url changes and renders associated component defined for the route aka url. You have to manually refresh or call a window function to reload.

att
  • 199
  • 3
  • 9
1

I dont know if you have already seen it but this answer could maybe help you or atleast give you an idea of how react solve this issues.

https://stackoverflow.com/a/39363278/9208722

1

You could implement something like this in your component:

import { inject, observer } from 'mobx-react';
import { observe } from 'mobx';

@inject('routerStore')
@observer
class PackageSummary extends React.Component {
  listener = null;
  componentDidMount() {
    this.listener = observe(this.props.routerStore, 'location', ({ oldValue, newValue }) => {
      if (!oldValue || oldValue.pathname !== newValue.pathname) {
        // your logic
      }
    }, true)
  }

  componentWillUnmount() {
    this.listener();
  }
}

Problem with this approach is that if you go back from /summary to other page (e.g. '/'), callback will initiate, so you would also need some kind of check which route is this. Because of these kind of complications I would suggest using mobx-state-router, which I found much better to use with MobX.

zhuber
  • 4,603
  • 2
  • 19
  • 50
  • I like this idea, it seems to be a way to observe the location property directly and act accordingly. I'm not familiar with mobx-state-router but I'll check that out as well. – Chris Tybur Jul 25 '19 at 00:10