Closed exinferis closed 7 years ago
If you don't want to pass SessionStore reference to other stores and keep them decoupled you will obviously need some shared middleman like event bus or observable state object.
With the latter it can look like this:
class SessionStore {
@observable privateState1 = []; // not accessible to other stores
@observable privateState2 = []; // not accessible to other stores
constructor(sharedStateTree) {
// Expose a derivation of private state
sharedStateTree.session = observable({
get publicState() {
return this.privateState1.concat(this.privateState2); // any time a relevant private state changes a public state is updated
}
})
}
}
class StatisticsStore {
constructor(sharedStateTree) {
// Use autorun/reaction/observe to react to changes
autorun(() => {
// This will run anytime sharedStateTree.session.publicState changes (which changes anytime privateState1 or privateState2 changes)
this.storeInfo(sharedStateTree.session.publicState);
})
// Or expose a derived state
sharedStateTree.statistics = observable({
get interestingStatistics() {
return sharedStateTree.session.publicState.join(", "); // anytime sharedStateTree.session.publicState changes interestingStatistics is updated
}
});
}
storeInfo(sessionStoreInfo) { / * */ }
}
Nice and fast answer! 🙏
The problem wasn't getting a reference to the session store, but that I hadn't groked the concept behind autorun
😅.
I was thinking obscure things about how autorun
knows when to run. (Like: It only runs for store changes in the same file.) When it just tracks the accessed props in the first run and reruns the function when those props are changed. (correct?)
We did it like this:
SessionStore.ts:
class SessionStore {
@observable session = null;
}
const sessionStore = new SessionStore();
export default sessionStore;
OtherStoreThatDependsOnSessionStore.ts:
import sessionStore from "./SessionStore.ts";
class OtherStoreThatDependsOnSessionStore {
@observable enabled = false;
constructor() {
autorun(() => {
this.setEnabled(!!sessionStore.session);
});
}
@action setEnabled(enabled) {
this.enabled = enabled;
// other init/cleanup stuff
}
}
Edit: formatting
When it just tracks the accessed props in the first run and returns the function when those props are changed. (correct?)
Yes (notice that render method in observers works basically the same)
We did it like this:
Don't do that. In general autorun should not contain any side effects (shouldn't change another state). You're duplicating the existing state and introducing another source of truth -> you can now change the enabled flag independently of the existence of a session and easily bring your app to an inconsistent state. Do it like this instead:
import sessionStore from "./SessionStore.ts";
class OtherStoreThatDependsOnSessionStore {
// enabled is now always in sync with the existence of a session
@computed get enabled() {
return !!sessionStore.session;
}
}
Btw I suggest to NOT using modules to inject stateful dependencies. Modules should contain definitions, not state. I know it's convinient and a lot of ppl does it, but it encourages a bad design (it's exactly the same as placing things on global/window) and introduces some technical problems (server side rendering, module cache invalidation, ...)
👍 👍
Are side-effects in the enabled
computation ok by design?
I'm thinking about something like resetting the containing store if the user session gets destroyed and requesting data to populate the stores when a new session gets created.
Are side-effects in the enabled computation ok by design?
No, it's the same situation. The "side effect" should be the computed value. I don't say the side effects in autorun/compute are forbidden, but you should be careful with them, the side effect means switching from reactive to imperative, which means becoming responsible for state synchronization (which you tried to avoid in the first place by using state management library) Consider synchronization of two arrays...you can observe one array and modify the second accordingly or the second can be expressed as a computation of the first. Similary you can observe SessionStore and synchronize the OtherStore accordingly or you can design the OtherStore to be a computation of SessionStore. Notice that result of computation itself is not kept in sync with the values it was derived from - an array A computed from array B is not updated when array B is changed. To obtain "up to date" array you have to "request the recomputation" by calling appropriate getter. So instead of trying to synchronize SessionStore with OtherStore, I suggest to have a (computed) getter for OtherStore, returning "fresh" OtherStore with current session. Anything depedent on OtherStore have to call this getter first, making sure everything is up to date... This is of course not possible if you export otherStore instance directly, you would have to export function or factory or avoid using import as dependency injection mechanism as I have already suggested.
You cannot put side effects in a derivation (computed values, rendering), it is forbidden and you will get an error if you try to do so. But you can absolutely use them in reactions since that's the whole point of it. The only thing you should be careful about when using autorun is that you can create infinite loops when you update some observable that will trigger the same autorun again, and again. MobX will produce a nice error about that too.
it is forbidden and you will get an error if you try to do so
I was referring to side effects placed into an action (that init method), in that case the computed woudn't throw I think... My point was about preferring reactive design instead of switching to imperative style via autorun/reaction/whatever.
Looks like this has been answered.
Thank you for your help and awesome work by the way! 👍 :)
Great answer ... helped me out 👍
Hi You don't need to use localStorage to solve the sharing or propagate changed from one store to another. jsbin link: http://jsbin.com/mocowopupi/5/edit?html,js,console,output
Simple solution is using the extends.`
const { observable, action, computed, autorun } = mobx;
class RootApp {
@observable data = 0;
constructor() {
setInterval(() => this.actionChangeData(), 1000)
}
@action actionChangeData = () => {
this.data++;
}
}
class App extends RootApp {
constructor() {
super();
}
@computed get chData() {
return this.data
}
}
const rootApp = new RootApp(); const app = new App(); autorun(() => { console.log(app.chData) }) `
I have a*:
We have a centralized SessionStore which holde the relevant user data beloging to the currently open session. If this store changes, or more specifically some values of this store, we need to update the data in other decoupled stores in our app (statistics, messages etc). How can we "listen" to changes in the session store in those other stores and possibly react (fetch new data) to it?