mobx computed / reactive context with event handlers? - javascript

With MobX, #computed properties are only cached when accessed from an observer or reactive context. So for example:
class Foo {
#computed
get somethingExpensive() { return someReallyExpensiveOperation() }
}
const f = new Foo();
setInterval(() => console.log(f.somethingExpensive), 1);
Will always call someReallyExpensiveOperation() because the computed is being called outside of a reactive context.
Is there a way to "enter" a reactive context to gain the benefits of #computed for setTimeout callbacks, EventEmitter event handlers etc?
EDIT: Another way to put this.. if I change the decorator to
class Foo {
#computed({ requiresReaction: true })
get somethingExpensive() { return someReallyExpensiveOperation() }
}
..it will throw when accessed from the setInterval example.

I use an instantly disposed autorun:
autorun((reaction) => {
console.log(f.somethingExpensive)
reaction.dispose()
})
Also made a npm module:
import runInReactiveContext from 'mobx-run-in-reactive-context'
// ...
runInReactiveContext(() => {
console.log(f.somethingExpensive)
})

Related

Proper way to rig up eventemitters for communication between a parent and object instances?

New to EventEmitter and ES6 classes, and I'm trying to grok the proper rigging. I have a "main" that instantiates an EventEmitter. In "main" I instantiate objects that I want to be able to listen for events emitted by "main"'s event emitter. I can pass a reference to main's EventEmitter to each object's constructor, but since the object's class is defined in a separate module file I think that it doesn't know that the ref is of type EventEmitter so within object's class I can't define an "on" listener on the ref to main's EventEmitter:
Main:
const listeningclassinstance = new ListeningClass(maineventemitter);
Submodule Class:
export default class ListeningClass {
constructor(maineventemitter) {
this.maineventemitter = maineventemitter;
}
maineventemitter.on("emittedevent", () => { // Visual Studio Code complains
onemittedevent();
});
onemittedevent() {
console.log("ListeningClass onemittedevent")
}
// ListeningClass
}
So here's what I rigged up, it's working although I know there's fancier getter/setter syntax in ES6. Also I'm passing in the EE3 class definition to the submodule's class after a single async call to load the EE3 module ... there might be a better way to handle that ...
Main:
// webpack dynamic import of EventEmitter3 here, setting a ref to EE3's class def
// the following Main code is actually within the dynamic import's promise then()
const listeningclassinstance = new ListeningClass(ee3class ,maineventemitter);
const listeningclassinstanceeventemitter = listeningclassinstance.getlisteningclasseventemitter();
listeningclassinstanceeventemitter.on("emittedeventfromlisteningclassinstance", () => {
console.log("main can hear event emitted from listeningclassinstance");
});
Submodule Class:
export default class ListeningClass {
constructor(ee3class,maineventemitter) {
this.maineventemitter = maineventemitter;
this.maineventemitter.on(
"emittedeventfrommain",
this.onemittedeventfrommain.bind(this);
this.eventemitter = new ee3class();
}
onemittedeventfrommain() {
console.log("ListeningClass emittedeventfrommain");
this.eventemitter.emit("emittedeventfromlisteningclassinstance");
}
getlisteningclasseventemitter() {
return this.eventemitter;
}
// ListeningClass
}

`this` isn't set to current object in monkey patched prototype method

I am trying to monkey patch an object created by react-test-renderer to add a .findById(testID) method, but inside the object I can't access other methods on the object.
Here is the relevant code in my test file.
import renderer from 'react-test-renderer'
beforeEach(() => {
//...
tree = renderer.create(<MyComponent {...props} />).root
Object.getPrototypeOf(tree).findById = function (testID) {
this.findAll((el) => {
return el.props.testID === testID
})
}
})
it('does something', () => {
const component = tree.findById('myTestId')
// ...
expect(/* something */)
})
Instead of having the findAll method on this it seems to be the global object. Why is this? I thought that if I call a method on an object that this will be set to that object when I use an old-school function function.

Add a callback to a Vue instance's destroy hook

I have a Map where the keys are instances of Vue components, like:
const instanceA = new Vue({...});
const instanceB = new Vue({...});
const register = new Map();
function registerInstance(instance){
register.set(instance, []);
}
How can I hook a callback to the destruction of a Vue component? I would like that when the component is being destroyed I would remove it's reference from my Map register. Something like:
function registerInstance(instance){
register.set(instance, []);
instance.onDestroy(() => {
register.set(instance, null);
register.delete(instance);
});
}
That would do a proper cleanup to avoid memory leaks, but not sure how to add a callback to a Vue instance's destruction process...
I do not want to add this logic in each component's beforeDestroy or destroyed functions. I would like to add them from outside the component, just using its instance pointer...
I thought about overriding instance.beforeDestroyed with a function that calls my code and then the original instance.beforeDestroyed from the instance. But that feels very wrong
OK, looks like we can use instance.$once("hook:beforeDestroy", () => { to add callbacks to hooks!
A example would be:
const instance = new Vue({});
instance.$once("hook:beforeDestroy", () => {
console.log('Destroying!');
});
setTimeout(() => {
instance.$destroy();
}, 1000);
<script src="https://vuejs.org/js/vue.min.js"></script>

Dispatch undefined after binding to class in redux

Issue with a pattern i'm trying to use with redux.
I have a a mapDispatchToProps as below,
const mapDispatchToProps = (dispatch) => {
return {
presenter: new Presenter(dispatch),
};
};
and my presenter constructor looks as below:
constructor(dispatch) {
this.dispatcher = dispatch;
}
If I check the value of it in the constructor and after it's set, all is well. However later when a method tries to use it, the value of dispatch is undefined.
If i save it to a var outside the class, i.e.
let dispatch;
class Presenter {
constructor(dispatcher) {
dispatch = dispatcher.bind(this)
}
}
I've tried using .bind() within the first constructor also but it keeps becoming undefined!
Class methods were of the form:
someMethod() {
//do stuff
}
which means they have their own this scope bound... I'd have to bind the individual methods in the constructor, such as:
constructor(dispatch) {
this.dispatch = dispatch;
this.someMethod = this.someMethod.bind(this);
}
Or turn them into => functions so they take their context from the surrounding class, i.e.
someMethod = () => dispatch(/* an action */);

Can I do dispatch from getters in Vuex

Fiddle : here
I am creating a webapp with Vue 2 with Vuex. I have a store, where I want to fetch state data from a getter, What I want is if getter finds out data is not yet populated, it calls dispatch and fetches the data.
Following is my Vuex store:
const state = {
pets: []
};
const mutations = {
SET_PETS (state, response) {
state.pets = response;
}
};
const actions = {
FETCH_PETS: (state) => {
setTimeout(function() {
state.commit('SET_PETS', ['t7m12qbvb/apple_9', '6pat9znxz/1448127928_kiwi'])
}, 1000)
}
}
const getters = {
pets(state){
if(!state.pets.length){
state.dispatch("FETCH_PETS")
}
return state.pets
}
}
const store = new Vuex.Store({
state,
mutations,
actions,
getters
});
But I am getting following error:
Uncaught TypeError: state.dispatch is not a function(…)
I know I can do this, from beforeMount of Vue component, but I have multiple components which uses same Vuex store, so I have to do it in one of the components, which one should that be and how will it impact other components.
Getters can not call dispatch as they are passed the state not context of the store
Actions can call state, dispatch, commit as they are passed the context.
Getters are used to manage a 'derived state'.
If you instead set up the pets state on the components that require it then you would just call FETCH_PETS from the root of your app and remove the need for the getter
I know this is an older post and I'm not sure if this is good practice, but I did the following to dispatch from a getter in my store module:
import store from "../index"
And used the store inside my getter like this:
store.dispatch("moduleName/actionName")
I did this to make sure data was made available if it was not already present.
*edit:
I want you to be aware of this: Vue form - getters and side effects
This is related to #storsoc note.
If you need to dispatch from your getter you probably are already implementing your state wrong. Maybe a component higher up should already have fetched the data before (state lifting). Also please be aware that getters should only be used when you need to derive other data from the current state before serving it to your template otherwise you could call state directly: this.$store.state.variable to use in methods/computed properties.
Also thing about your lifecycle methods.. you could for example in your mounted or created methods check if state is set and otherwise dispatch from there. If your getter / "direct state" is inside a computed property it should be able to detect changes.
had the same Problem.. also wanted all Vue-Instances to automaticly load something, and wrote a mixin:
store.registerModule('session', {
namespaced: true,
state: {
session: {hasPermission:{}},
sessionLoaded:false
},
mutations: {
changeSession: function (state, value)
{
state.session = value;
},
changeSessionLoaded: function (state)
{
state.sessionLoaded = true;
}
},
actions: {
loadSession(context)
{
// your Ajax-request, that will set context.state.session=something
}
}
});
Vue.mixin({
computed: {
$session: function () { return this.$store.state.session.session; },
},
mounted:function()
{
if(this.$parent==undefined && !this.$store.state.session.sessionLoaded)
{
this.$store.dispatch("session/loadSession");
this.$store.commit("changeSessionLoaded");
}
},
});
because it loads only one per vue-instance and store and it it inlcuded automaticly in every vue-instance, there is no need to define it in every main-app
I use a getter to configure a dynamic page. Essentially, something like this:
getter: {
configuration: function () {
return {
fields: [
{
component: 'PlainText',
props: {},
setPropsFromPageState: function (props, pageState, store) {
// custom logic
}
}
]
};
}
}
Then in the page component, when I am dynamically setting the props on a dynamic component, I can call the setPropsFromPageState(field.props, this.details, this.$store) method for that component, allowing logic to be set at the config level to modify the value of the props being passed in, or to commit/dispatch if needed.
Basically this is just a callback function stored in the getter that is executed in the component context with access to the $store via it.

Categories

Resources