Let\'s take a class like this in an app with React and React Router.
@observer class Module1 extends React.Component {
constructor (props) {
super(props);
One week ago we started a new project with with react and mobx, and I faced the same issue as yours. After looking around I found the best way is to use react's context. Here's how:
The store: stores/Auth.js
import { get, post } from 'axios';
import { observable, computed } from 'mobx';
import jwt from 'jsonwebtoken';
import singleton from 'singleton';
import Storage from '../services/Storage';
class Auth extends singleton {
@observable user = null;
@computed get isLoggedIn() {
return !!this.user;
}
constructor() {
super();
const token = Storage.get('token');
if (token) {
this.user = jwt.verify(token, JWT_SECRET);
}
}
login(username, password) {
return post('/api/auth/login', {
username, password
})
.then((res) => {
this.user = res.data.user;
Storage.set('token', res.data.token);
return res;
});
}
logout() {
Storage.remove('token');
return get('/api/auth/logout');
}
}
export default Auth.get();
Note: we are using singleton to make sure that it's one instance only, because the store can be used outside react components, eg. routes.js
The routes: routes.js
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import App from './App';
import Login from './Login/Login';
import Admin from './Admin/Admin';
import Dashboard from './Admin/views/Dashboard';
import Auth from './stores/Auth'; // note: we can use the same store here..
function authRequired(nextState, replace) {
if (!Auth.isLoggedIn) {
replace('/login');
}
}
export default (
);
The main component: App.js
// App.js
import React, { Component } from 'react';
import Auth from './stores/Auth';
export default class App extends Component {
static contextTypes = {
router: React.PropTypes.object.isRequired
};
static childContextTypes = {
store: React.PropTypes.object
};
getChildContext() {
/**
* Register stores to be passed down to components
*/
return {
store: {
auth: Auth
}
};
}
componentWillMount() {
if (!Auth.isLoggedIn) {
this.context.router.push('/login');
}
}
render() {
return this.props.children;
}
}
And finally, a component using the store: Login.js
import React, { Component } from 'react';
import { observer } from 'mobx-react';
@observer
export default class Login extends Component {
static contextTypes = {
router: React.PropTypes.object.isRequired,
store: React.PropTypes.object.isRequired
};
onSubmit(e) {
const { auth } = this.context.store; // this is our 'Auth' store, same observable instance used by the `routes.js`
auth.login(this.refs.username.value, this.refs.password.value)
.then(() => {
if (auth.isLoggedIn) this.context.router.push('/admin');
})
.catch((err) => {
console.log(err);
});
e.preventDefault();
}
render() {
return (
Login
);
}
}
You can declare new stores and add them in getChildContext
of App.js
, and whenever you need a certain store just declare the store
dependency in the component's contextTypes
, and get it from this.context
.
I noticed that it's not a requirement to pass an observable as prop, just by having the @observer
decorator and using any observable value in your component, mobx
and mobx-react
do their magic.
By the way redux's
does the same thing as explained in App.js
. https://egghead.io/lessons/javascript-redux-passing-the-store-down-implicitly-via-context
Reference: