Vue composite make function reactive? - vue.js

Is it possible to create a composite function such that consumer can provide a reference to a function? In mixins you could just call the consumer method as long as it existed. Here is how I would think this would work but it doesn't. method1 is not reactive. I realize this might be an antipattern. Sometimes you run an async/ajax call and once it's done it would be nice to just run the callback method1 instead of changing a dummy value and running a watch on it in the consumer. Ideas?
-- consuming component
setup() {
let { prop1, method1, method2 } = useComposite();
method1 = (o) =>
{
console.log(o);
};
method2();
}
composable function --
export default function useComposite()
{
let method1 = ref(()=>{});
const method2 = () =>
{
method1.value("hello");
};
const prop1 = ref(o);
return {
method1,
methoid2,
prop1,
};
}

method1 is already a ref and used as such. If it's redefined, this should be done like:
method1.value = (o) => ...
This is definitely an antipattern. There could be multiple callbacks, while it's limited to one. A more complete solution could use event bus like mitt and register handlers for an event.
A proper solution with composition API would involve a watcher. Nothing requires composition API here. It's just callback-based control flow and a step back from promise-based one. If method2 is asynchronous, it needs to return a promise and be chained in a place it's used:
const method1 = (o) => ...
method2().then(method1);

Related

Is it valid inside a getter, call an action in vuex?

For example, I use a Getter for write a logic about a if object has a especify content.
const state = {
object = []
}
const getters = {
CHECK_OBJECT_STATE: (state) => {
if(Object.prototype.hasOwnProperty.call(state.object, "error")){
//return a message, in this case is not necessary proceed the logic.
}else{
//otherwise I need take state.object.id and fetch a new a action.
//CALL ACTION FETCH_OBJECT_ID
}
}
}
const actions = {
FETCH_OBJECT(...){
//SET MUTATION HERE
}
FETCH_OBJECT_ID (..) {..}
}
anyone can suggest me other method to do it, if it's not valid?
Vuex Getters are not designed for asynchronous tasks, those should be handled in the actions. I really think that getters operate best as pure functions which perform read-only operations on an existing state, so it should do only one job: retrieve the data in the state.
So you process the logic, the if statement in the actions, store the returned value in a state array object (which is pretty weird for me to name an array "object"), and the getter only has to retrieve such array. Things are reactive so that getter will always hold the latest data set.

How to make a debounce function act independently on different instances of the same component on Vue.js?

I have two Vue components that have a "save to disk" call on every change of data, it is loaded into these components via a mixin and each component save into a different file, so they must function independently (only trigger a debounce reset on its own change of data). To prevent too much writing to the disk. Here's my debound function:
function debounce(fn, delay) {
var timeoutID = null;
return function () {
console.log("clearing " + timeoutID)
clearTimeout(timeoutID);
var args = arguments;
var that = this;
timeoutID = setTimeout(() => fn.apply(that, args), delay);
};
}
Here's the methods on my mixin that the components inherit:
methods: {
saveData: debounce(function(){
console.log('saving widget: ' + this.$parent.widget.id);
this.saver.store = this.persisted;
}, 5000),
},
It works well when I'm changing data on one or the other component, but when I change data on one and before debounce ends I change on the other, it cancels my debounce function from the first one, and only saves the second component data.
What am I missing here?
I've encountered the same issue today. I've tried something else that is, in my opinion, a bit cleaner. Instead of defining the debounced function within the methods block like you did, try defining it as part of the data like so:
data() {
return {
saveData: debounce(function(){
console.log('saving widget: ' + this.$parent.widget.id);
this.saver.store = this.persisted;
}, 5000)
}
}
You can call the method the same way as you would normally. From the docs: "A component’s data option must be a function, so that each instance can maintain an independent copy of the returned data object." This way, each instance that uses the debounce function will have it's own unique instance of it.
More on how this works can be found here: https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function
That's because each component instance shares the same debounced function, only the context (this, that) is varying.
A simple workaround would be to change your debounce implementation to
function debounce(fn, delay) {
var thatUidToTimeoutID = {};
return function () {
var args = arguments;
var that = this;
clearTimeout(thatUidToTimeoutID[that._uid]);
thatUidToTimeoutID[that._uid] = setTimeout(() => fn.apply(that, args), delay);
};
}
_uid is holding an unique id of each component, it's more of an internal property (hence it's weird key) but it should be good enough.

RN with Firestore unable to wait for Promise to resolve

I have a simple call to Firestore to write a doc and then wait for the doc to finish writing before changing state of the parent. However, the parent state is being changed too fast, resulting in reading fields that I think have not yet been written/propagated. I tried adding a delay with setTimeout and it seems ignored. How can I make sure the state change is absolutely only called after the Firestore doc is written completely?
The code:
updateDBEntry(stateObj) {
var that = this;
var docRef = firebase.firestore().collection('sessions').doc(this.state.userID);
docRef.get().then((doc) => {
if (!doc.exists) {
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
var duration = (stateObj.seshDuration) ? stateObj.seshDuration : 1;
docRef.set({
seshName: stateObj.seshName,
seshStreet: stateObj.seshStreet,
seshZipcode: stateObj.seshZipcode,
seshDuration: duration,
seshDesc: stateObj.seshDesc,
seshTime: timestamp,
}).then(() => {
var handleToUpdate = that.props.handleToUpdate;
setTimeout(() => {
handleToUpdate(1); //this changes the parent's state
}, 10000);
});
}
});
}
I'm not sure exactly the problem you're running into here, mostly because you've only shown this one function, and not how you're using it in the rest of your app. But I can tell you three things for sure:
When the promise from set() resolves successfully, you can be certain the document is written.
get() and set() are asynchronous, and so is then(). They all return promises the represent async work.
Item 2 means that your entire function updateDBEntry() is also asynchronous and returns immediately, before any of the work is complete.
Because this entire function is async, when it returns, the document will not have been created yet. Instead, maybe this function should return that resolves only after all the work is complete, so that the caller can also use it set up some code to execute after the work is done.

How jasmine spy example works

All;
I am just starting learning Jasmine( version 2.0.3 ), when I got to Spies section, the first example confused me:
describe("A spy", function() {
var foo, bar = null;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
}
};
spyOn(foo, 'setBar');
foo.setBar(123);
foo.setBar(456, 'another param');
});
it("tracks that the spy was called", function() {
expect(foo.setBar).toHaveBeenCalled();
});
it("tracks all the arguments of its calls", function() {
expect(foo.setBar).toHaveBeenCalledWith(123);
expect(foo.setBar).toHaveBeenCalledWith(456, 'another param');
});
it("stops all execution on a function", function() {
expect(bar).toBeNull();
});
});
I wonder if anyone could explain why the setBar function does not affect the bar defined inside describe block? How Jasmine spies deal with this?
Thanks
Because you are not actually executing the methods.
If you want this test to fail:
it("stops all execution on a function", function() {
expect(bar).toBeNull();
});
After these calls:
foo.setBar(123);
foo.setBar(456, 'another param');
Then you should call and.callThrough for your spy.
spyOn(foo, 'setBar').and.callThrough();
From the documentation
Spies: and.callThrough
By chaining the spy with and.callThrough, the spy will still track all
calls to it but in addition it will delegate to the actual
implementation.
With regard to your question, 'how jasmine deals with this?'
From here you can read a basic explanation:
Mocks work by implementing the proxy pattern. When you create a mock
object, it creates a proxy object that takes the place of the real
object. We can then define what methods are called and their returned
values from within our test method. Mocks can then be utilized to
retrieve run-time statistics on the spied function such as:
How many times the spied function was called.
What was the value that the function returned to the caller.
How many parameters the function was called with.
If you want all of the implementation details, you can check the Jasmine source code which is Open Source :)
In this source file CallTracker you can see how the gather data about the method calls.
A little more about the proxy pattern.

How to override dojo's domReady

I want to override dijit._CssStateMixin's domReady() method.
Is there any way to override that instead of changing the listener mechanism in Dojo.
I tried overriding _cssMouseEvent() method in simple javascript, but it still does invoke dijit's _cssMouseEvent() from domReady().
I have tried following approach:
dojoConfig = {
map: {
'dijit/_CssStateMixin': {
'dojo/domReady': 'app/noop'
}
}
};
I have added 'app' folder and then 'noop.js' inside that.
noop.js has nothing in it:
define([], function () {
return function () {};
});
Even after this I can see that dijit.js's _CssStateMaxin domReady() getting called from listener.apply (code snippet pasted below)
var addStopImmediate = function(listener){
return function(event){
if(!event.immediatelyStopped){// check to make sure it hasn't been stopped immediately
event.stopImmediatePropagation = stopImmediatePropagation;
return listener.apply(this, arguments);
}
};
}
If your ultimate goal is to prevent the domReady callback in dijit/_CssStateMixin from running, your simplest bet is likely to re-map dojo/domReady to a different module that doesn't call the callback at all, when loaded via dijit/_CssStateMixin.
NOTE: Stripping out these handlers might have adverse visual effects on Dijit widgets which inherit _CssStateMixin, since it may hinder the application of Dijit CSS classes related to hover and focus. But if your concern is that _CssStateMixin is hampering performance, it may at least be worth a try to confirm or deny your suspicion.
First we have to create a simple module that returns a function that does nothing, which we will later substitute for dojo/domReady when loaded by dijit/_CssStateMixin, so that it can still call domReady but it won't execute the callback it passes.
For simplicity's sake I'll assume you already have a custom package that you can easily add a module to; for this example I'll assume it's called app. Let's create app/noop:
define([], function () {
return function () {};
});
Now let's configure the loader to map app/noop in place of dojo/domReady specifically when loaded by dijit/_CssStateMixin:
var dojoConfig = {
...,
map: {
'dijit/_CssStateMixin': {
'dojo/domReady': 'app/noop'
}
},
...
};
Now the offending domReady callback should no longer be run.
If you're curious about map, you can read more about it in this SitePen FAQ.