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>