When I use the mobx-react ,I use inject decorator to transmit the store.But when I get the store such as
#inject("store") #observer
class item extends Component {
constructor() {
this.store = this.props.store;
}
}
But when I want to call the function of store such as store.getUser() , I found that the context getUser function is not this , how can I bind this to the store ?
PS: the store is such as following :
class Store {
#observable user = "Sarah";
#computed
get getUser() {
return user + "Ok";
}
}
export default new Store();
I use the getUser like
render() {
<div>{this.store.getUser()}</div>
}
class Store {
#observable user = "Sarah";
#computed
get okUser() {
return this.user + "Ok";
}
}
const store = new Store();
console.log(store.okUser);
#computed is getter so you do not need to call it as function.
You have to use this.user in your store:
class Store {
#observable user = "Sarah";
#computed
get getUser() {
return this.user + "Ok";
}
}
export default new Store();
A computed is a getter, so you don't access it with a function call. Just dereference the field:
render() {
<div>{this.store.getUser}</div>
}
Related
I have two MobX stores:
export default class AccountStore {
accounts : Account[] = [];
constructor() {
makeAutoObservable(this);
}
loadAccounts = async () => {
//call to loadOthers
}
}
and
export default class OtherStore {
others : Others[] = [];
constructor() {
makeAutoObservable(this);
}
loadOthers = async () => {...}
}
In my AccountStore class, in my loadAccounts function I want to make a call to loadOthers from the other MobX store. How can I make this call?
Depends on how you initialize your stores. The most simple way is to have singleton stores, that way you can just import it directly.
Another way is you have some sort of root store, which initializes all other store, and pass itself to every store too, that way you have reference to the root store and to every other store from any store. Something like that:
class RootStore {
constructor() {
this.accountStore = new AccountStore(this)
this.otherStore = new OtherStore(this)
}
}
class AccountStore() {
constructor(rootStore) {
this.rootStore = rootStore.
}
loadAccounts = async () => {
this.rootStore.otherStore.callOthers()
}
}
Third way is just pass OtherStore instance to loadAccounts function. For example if you want to call loadAccounts when some React component mounts you can do that in useEffect, just get both store, and pass one to another:
export default class AccountStore {
// ...
loadAccounts = async (otherStore) => {
// ...
otherStore.loadOthers()
}
}
How can we use a mobx store in utility function?
I have a mobx store and a utility function to make a axio call, I want to use stote value in the utility, how can I do this?
// Store example
export default class SampleStore {
#observable title = "Coding is Love";
#observable user = {
userId: 1,
};
#action
setUser(user) {
this.user = user;
}
#action
updateUser(data) {
this.user = { ...this.user, ...data };
}
#action
clearUser() {
this.user = undefined;
}
#action
setTitle(title) {
this.title = title;
}
}
// Utility function in different file
export function makeApiCall () {
// Use SampleStore here
}
Depends on how you actually initialize your store, how your app is organized and many other factors.
Most simple way is to have singleton store and then you just import it and use directly:
// export an instance instead
export const sampleStore = new SampleStore()
// .. util fil
import { sampleStore } from './SampleStore.js'
export function makeApiCall () {
sampleStore.setUser()
}
Another way is just to pass store to the function, for example if you want to make this call inside useEffect or something:
// Make function accept store as an argument
export function makeApiCall (sampleStore) {
sampleStore.setUser()
}
// ... inside of some React component
// get store from the context (I guess you will have it at that point)
const { sampleStore } = useStores()
useEffect(() => {
// and just pass to the function
makeApiCall(sampleStore)
}, [])
in my react native app I want to use mobx for state management, my store is divided in multiple stores/files and since I want to be able to call a store actions from another stores I'm implementing a GlobalStore where I instantiate the other stores.
I want to be able to do something like this from my components
import { PostStore } from '../stores/PostStore.js'
import { UserStore } from '../stores/UserStore.js'
import { VenueStore } from '../stores/VenueStore.js'
class GlobalStore
{
postStore = new PostStore(this);
userStore = new UserStore(this);
venueStore = new VenueStore(this);
}
export default new GlobalStore;
This makes it so that using react-native Context-Provider API I can call every store action in ALL my compoennts using globalStore as a link:
In any component I can do:
globalStore.postStore.listPosts()
However I'm still not sure how I can access other store actions from within OTHER STORES.
What if inside postStore I want to use spinnerStore (to show axios calls pending, error or success status):
#action.bound getPosts = flow(function * (payload)
{
this.spinnerStore.setData({status: 1});
try
{
this.spinnerStore.setData({status: 2, response: response});
let response = yield axios.get('/api/posts', { params: payload })
return response;
}
catch (error) {
console.error(error);
this.spinnerStore.setData({ status: 3, errors: error });
throw error;
}
})
Here spinnerStore would be undefined...
However I'm still not sure how I can access other store actions from within OTHER STORES.
When you instantiate a store you can assign one of its properties to be another store instance. This is what you included in your example
class Foo {
constructor(instance) {
this.instance = instance
}
}
class Bar {}
const foo = new Foo(new Bar());
foo.instance instanceof Bar; // true
Now your foo instance has access to any public properties/methods defined on Bar
class Foo {
constructor(instance) {
this.instance = instance
}
}
class Bar {
#observable isLoading = false;
#observable data = null;
#action
getData() {
this.isLoading = true;
return axios.get('/foo').then((data) => {
this.isLoading = false;
this.data = data;
}).catch(e => {
this.isLoading = false;
});
}
}
const foo = new Foo(new Bar());
// in react
const MyComponent = ({ foo }) => (
<div>
{foo.instance.isLoading && <Spinner />}
<button onClick={foo.instance.getData}>
will call Bar action from Foo store
</button>
</div>
);
export default lodash.flowRight(
mobx.inject((stores) => ({ foo: stores.fooStore })),
mobx.observer
)(MyComponent)
In your example with generators, you cannot use fat arrows so this isn't bound to your class instance anymore, which is why it will be undefined. Using promises and fat arrows solves that problem.
I am using mobx-react-lite with hooks.
I have two store.
AuthStore
SomeOtherStore
This is my dummy AuthStore
import { observable, decorate, action } from 'mobx';
import { createContext } from 'react';
import { ROLE_LOGISTICS_MANAGER } from '../settings/constants';
import AuthService from '../services/AuthService';
class AuthStore {
constructor() {
this.authService = new AuthService();
}
currentMode = ROLE_LOGISTICS_MANAGER;
authenticating = true;
isLoggedIn = false;
userId = null;
loginLoading = false;
login = async params => {
this.loginLoading = true;
try {
const data = await this.authService.loginAsync(params);
this.loginLoading = false;
this.isLoggedIn = true;
} catch (e) {
console.error(e);
this.loginError = e;
} finally {
this.loginLoading = false;
}
};
}
decorate(AuthStore, {
currentMode: observable,
loginLoading: observable,
isLoggedIn: observable,
authenticating: observable,
userId: observable,
fetchUser: action,
login: action
});
export const AuthStoreContext = createContext(new AuthStore());
Now Lets say I want to change isLoggedIn from another store,
How can I do that? I tried to find ways in docs, couldn't find a solid solution.
I am using hooks with mobx-react-lite
So normally I use mobx like
const authStore = useContext(AuthStoreContext);
It's a common pattern to have stores as properties on a RootStore, each having references back to the root. So you could have a structure like:
class RootStore {
constructor (auth, ui) {
this.auth = new AuthStore(this)
this.ui = new UiStore(this)
}
}
class AuthStore {
constructor (rootStore) {
this.rootStore = rootStore
}
logout() {
this.isLoggedIn = false
}
}
decorate(AuthStore, {
logout: action
})
Then, when you need to call a function on another store, you can use the reference to the root as a pathway. The pattern's described in more detail here. A possible example of use with useContext might be:
const { someStore } = useContext(rootStoreContext)
someStore.rootStore.auth.logout()
Problem: shouldComponentUpdate retrieves previous state with this.state, that doesn't work if you keep reference to array at UserList, and update array entity at UserStore.
PureRenderMixin.js
const deepEqual = require('deep-equal');
module.exports = function pureRenderMixin(Component) {
Component.prototype.shouldComponentUpdate = function(nextProps, nextState) {
return !deepEqual(this.props, nextProps) || !deepEqual(this.state, nextState);
};
return Component;
};
UserList.react.js
class UserList extends React.Component {
constructor(props) {
super(props);
this._onChange = this._onChange.bind(this);
}
componentWillMount() {
UsersStore.addChangeListener(this._onChange);
}
_onChange() {
this.setState({userList: UsersStore.getState()});
}
}
module.exports = PureRenderMixin(UserList);
UsersStore.js
......
getState() { return _userList; }
switch(action.type) {
case ActionTypes.UPDATE_USER_FLAG:
//!!!!!!!!!!!!!!
//PROBLEM: since UserList.react keep userList reference, there is no way to retrieve previous state inside shouldComponentUpdate
_userList[action.index].flag = action.flag;
UsersStore.emitChange();
break;
}
#taggon solution
thanks to taggon, now I know how to make shouldComponentUpdate keep the reference to previous state:
UsersStore.js
......
getState() { return _userList; }
switch(action.type) {
case ActionTypes.UPDATE_USER_FLAG:
//SOLUTION: copy an array, so there will be two versions of _userList[action.index]
_userList = _.map(_userList, _.clone);
_userList[action.index].flag = action.flag;
UsersStore.emitChange();
break;
}
I think the problem is in the store.
The store would be better to create another array whenever its state is changed.
For example, think the store as an array:
var store = [ ];
export default store;
You may want to write add() function like this:
export function add(newItem) {
store = [ ...store, newItem ];
// or write this in es5
store = store.concat([newItem]);
// trigger change event or dispatch an action here
}
Likewise, remove() function can be:
export remove(index) {
store = [ ...store.slice(0, index), ...store.slice(index+1) ];
// trigger change event or dispatch an action here
}
In this way, the store dereference the component's state whenever the store is changed. This makesshouldComponentUpdate() return true.
I hope this helps you.
if your props are immutable, you can compare the data by reference safely and easily. you can have a look at immutablejs
class ProductStore extends ReduceStore {
getInitialState() {
return Immutable.OrderedMap({1: new Product('react', 'flux'), 2: new Product('angular', 'mvc')});
}
reduce (state, action) {
switch (action.type) {
case 'product/item-selected':
return state.map((product)=> {
return product.set('selected', product.id === action.id);
});
case 'product/search':
let alldata = this.getInitialState();
return alldata.filter((product)=> {
return product.name.indexOf(action.value) !== -1;
});
default:
return state;
}
}
}
export default class ProductDetail extends Component {
shouldComponentUpdate(nextProps) {
return this.props.product !== nextProps.product;
}
render() {
const {product} = this.props;
return (
<div className="product-detail">
<div className="product-name">
{product.name}
</div>
<div className="product-type">
{product.type}
</div>
</div>
);
}
}
https://facebook.github.io/immutable-js/