1

As with all things react I'm trying to do something simple and I'm guessing I'm missing some obvious configuration wise all I'm trying to do is take a redux app and implement mobx.

My issue is that I trying to go to the route /user/12345

The store is being called - I am getting data back from the API but I'm getting a few exceptions the first is from mobx

An uncaught exception occurred while calculation your computed value, autorun or tranformer. Or inside the render().... In 'User#.render()'

Then as is somewhat expected a null value is blowing up in the presenter because the store is not yet loaded

 Uncaught TypeError: Cannot read property 'name' of null

Then in the store itself because of the returned api/promise where my user is being set a mobx exception

Uncaught(in promise) Error: [mobx] Invariant failed: It is not allowed to create or change state outside an `action`

I have added @action to the store function that is calling the api so I'm not sure what I've got messed up - and before I bubble gum and hodge podge a fix I would rather have some feedback how to do this correctly. Thanks.

UserStore.js

import userApi from '../api/userApi';
import {action, observable} from 'mobx';

class UserStore{
   @observable User = null;

   @action getUser(userId){
      userApi.getUser(userId).then((data) => {
         this.User = data;
      });
   }
}

const userStore = new UserStore();
export default userStore;
export{UserStore};

index.js

import {Router, browserHistory} from 'react-router';
import {useStrict} from 'mobx';
import routes from './routes-mob';

useStrict(true);
ReactDOM.render(
    <Router history={browserHistory} routes={routes}></Router>,
    document.getElementById('app')
);

routes-mob.js

import React from 'react';
import {Route,IndexRoute} from 'react-router';
import App from './components/MobApp';
import UserDetail from './components/userdetail';

export default(
    <Route name="root" path="/" component={App}>
       <Route name="user" path="user/:id" component={UserDetail} />
    </Route>
);

MobApp.js

import React,{ Component } from 'react';
import UserStore from '../mob-stores/UserStore';

export default class App extends Component{
   static contextTypes = {
      router: React.PropTypes.object.isRequired
   };

   static childContextTypes = {
      store: React.PropTypes.object
   };

   getChildContext(){
      return {
         store: {
            user: UserStore
         }
      }
   }

   render(){
      return this.props.children;
   }

}

.component/userdetail/index.js (container?)

import React, {Component} from 'react';
import userStore from '../../mob-stores/UserStore';
import User from './presenter';

class UserContainer extends Component{
   static childContextTypes = {
       store: React.PropTypes.object
   };

   getChildContext(){
      return {store: {user: userStore}}
   }

   componentWillMount(){
      if(this.props.params.id){
         userStore.getUser(this.props.params.id);
      }
   }

   render(){
      return(<User />)
   }
}

export default UserContainer;

.component/userdetail/presenter.js

import React, {Component} from 'react';
import {observer} from 'mobx-react';

@observer
class User extends Component{
   static contextTypes = {
      store: React.PropTypes.object.isRequired
   }

   render(){
      const {user} = this.context.store;
      return(
          <div>{user.User.name}</div>
      )
   }
}

Forgive me if its a little messy its what I've pieced together for how to implement mobx from various blog posts and the documentation and stack overflow questions. I've had a hard time finding a soup-to-nuts example that is not just the standard todoapp

UPDATE

Basically the fix is a combination of @mweststrate answer below adding the action to the promise response

@action getUser(userId){
  userApi.getUser(userId).then(action("optional name", (data) => {
     // not in action
     this.User = data;
  }));

}

and including a check in the presenter that we actually have something to display

<div>{this.context.store.user.User ? this.context.store.user.User.name : 'nada' }</div>
4

1 に答える 1