4
votes

I am trying to have react router v4 navigate from an action in a store without explicitly passing the history prop to each store action that needs to navigate after some logic runs.

I am looking for something like this to work:

import { NavigationStore } from 'navigation-store';

class ContactStore {
  @action contactForm(name, message){
    // Some logic runs here

    // Then navigate back or success

    // Go back
    NavigationStore.goBack();

    // Or Navigate to Route
    NavigationStore.push('/success'); 
  }
}

Of course NavigationStore doesn't exist (I wouldn't know what to put in it from React Router to make it work), but I am looking to import a mobx navigation store that I can use to navigate to and from anywhere in the app (component or store) with the same api as react-router

How to do it?

Update:

RR4 doesn't give us a way to navigate from a store's action. I am trying to navigate just like above using RR4. I just need to know what navigation-store should contain to so I can:

  1. import { NavigationStore } from 'navigation-store'; anywhere (component/store), also history aware, to be able to navigate from anywhere.
  2. NavigationStore.RR4Method(RR4MethodParam?); where RR4Method would be the available RR4 navigation options like push, goBack, etc.. (This is how navigation should happen)

Update 2:

So the url updates now from the NavigationStore.push('/success'); but no webpage refresh happens.

Here is navigation-store.js

import { observable, action } from 'mobx'
import autobind from 'autobind-decorator'
import createBrowserHistory from 'history/createBrowserHistory'

class NavigationStore {
  @observable location = null;
  history = createBrowserHistory();

  @autobind push(location) {
    this.history.push(location);
  }
  @autobind replace(location) {
    this.history.replace(location);
  }
  @autobind go(n) {
    this.history.go(n);
  }
  @autobind goBack() {
    this.history.goBack();
  }
  @autobind goForward() {
    this.history.goForward();
  }
}

const navigationStore = new NavigationStore();

export default navigationStore;

Here is app.js

import React from 'react'
import { Provider } from 'mobx-react'
import { BrowserRouter as Router, Route } from 'react-router-dom'
import Contact from 'screens/Contact'
import Success from 'screens/Success'

export default class App extends React.Component {
  render() {
    return (
      <div>
        <Provider>
          <Router>
            <div>
              <Route path='/contact' component={Contact} />
              <Route path='/success' component={Success} />
            </div>
          </Router>
        </Provider>
      </div>
    );
  }
}

Once the url changes to /success, nothing happens to the webpage instead of loading the matching component Success in this case. Still stuck here..

Update 3 (solution):

This helped I am putting it here as a reference for others as it was very frustrating for me.

In app.js I had to change:

import { BrowserRouter as Router, Route } from 'react-router-dom'
<Router>

to

import { Route } from 'react-router-dom'
import { Router } from 'react-router'
import navigationStore from 'stores/navigation-store'
<Router history={navigationStore.history}>

I hope this helps others :)

1

1 Answers

5
votes

You can always use a store as a constructor argument to another store. Then you'll be able to use the NavigationStore instance correctly.

So where you initialize your stores you can have:

const navigationStore = new NavigationStore(); 
const contactStore = new ContactStore(navigationStore)
const stores = {
    navigationStore, 
    contactStore
}

And then your contact store becomes:

class ContactStore {
  _navigationStore; 
  constructor(navigationStore) {
    this._navigationStore = navigationStore;
  }
  @action contactForm(name, message){
    // Some logic runs here

    // Then navigate back or success

    // Go back
    this._navigationStore.goBack();

    // Or Navigate to Route
    this._navigationStore.push('/success'); 
  }
}

Edit

You can also use a singleton in order to export the instance of the store, and thus use it by importing it in any module. So you could have something like this:

navigationStore.js

class NavigationStore() {
    goBack() {...}; 
    push(target) {...};
}
const navigationStore = new NavigationStore();
// The trick here is to export the instance of the class, not the class itself
// So, all other modules use the same instance.  
export default navigationStore;

contactStore.js

import navigationStore from 'navigation-store';
// We import the naviationStore instance here

class ContactStore {
  @action contactForm(name, message){
    // Some logic runs here

    // Then navigate back or success

    // Go back
    navigationStore.goBack();

    // Or Navigate to Route
    navigationStore.push('/success'); 
  }
}

App.js: Where you initialize the stores for the mobx provider:

import navigationStore from './navigationStore'; 
// We import the same instance here, to still have it in the provider.
// Not necessary if you don't want to inject it. 

const stores = {
    navigationStore, 
    contactStore: new ContactStore();
}