Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to connect state to props with mobx.js @observer when use ES6 class?

Tags:

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);
    //...
  }

  componentWillMount(){
    //...
  }

  method(){
    //...
  }

  otherMethod(){
    //...
  }

  render() {
    return (
       <ChildComp bars={this.props.bars}/>}
    );
  }
}

And let's take a state like this

state = observable({
  module1:{
    bars:{
      //...
    }
  },
  module2:{
    foos:{
      //...
    }
  }
})

The Module1 component is loaded like this:

//index.js
render(
      <Router history={browserHistory}>
        <Route path="/" component={App}>
          <Route path='/map' component={Module1} >
            <Route path="/entity/:id" component={SubModule}/>
          </Route>
          <Route path='/map' component={Module2} >
        </Route>
      </Router>,
      document.getElementById('render-target')
    );

How could I pass the props module1.bars to Module1 component? In redux I would use <provider>and redux-connect but I am a bit lost with this in Mobx.js.

like image 319
dagatsoin Avatar asked Mar 07 '16 18:03

dagatsoin


People also ask

How do you integrate MobX With react?

While MobX works independently from React, they are most commonly used together. In The gist of MobX you have already seen the most important part of this integration: the observer HoC that you can wrap around a React component. observer is provided by a separate React bindings package you choose during installation.

What is observer in MobX?

The observer function / decorator can be used to turn ReactJS components into reactive components. It wraps the component's render function in mobx. autorun to make sure that any data that is used during the rendering of a component forces a re-rendering upon change.

How does MobX react work?

MobX reacts to any existing observable property that is read during the execution of a tracked function. "reading" is dereferencing an object's property, which can be done through "dotting into" it (eg. user.name ) or using the bracket notation (eg. user['name'] , todos[3] ) or destructuring (eg.

Can I use MobX in react native?

With the introduction of the new Context API, there is no need to use Provider pattern with MobX and you can now just use the React Context API. Also the example uses mobx-react-lite but you can use the normal mobx-react .


1 Answers

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 (
  <Route name="root" path="/" component={App}>
    <Route name="login" path="login" component={Login} />
    <Route name="admin" path="admin" onEnter={authRequired} component={Admin}>
      <IndexRoute name="dashboard" component={Dashboard} />
    </Route>
  </Route>
);

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 (
      <div className="login__form">
        <h2>Login</h2>
        <form onSubmit={this.onSubmit.bind(this)}>
          <input type="text" ref="username" name="username" placeholder="Username" />
          <input type="password" ref="password" name="password" placeholder="Password" />
          <button type="submit">Login</button>
        </form>
      </div>
    );
  }
}

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 <Provider store={myStore}><App /></Provider> does the same thing as explained in App.js. https://egghead.io/lessons/javascript-redux-passing-the-store-down-implicitly-via-context

Reference:

  • http://mobxjs.github.io/mobx/refguide/observer-component.html
  • https://facebook.github.io/react/docs/context.html
like image 159
thaerlabs Avatar answered Oct 04 '22 06:10

thaerlabs