JavaScript: Cancelling ondragstart asynchronously - javascript

I have the following code, which doesn't work.
export function makeDraggable(htmlElement: any, dotnetCallbackObject: any): void {
let onDragStart = async (event: any) => {
let mayDrag: boolean = await someAsyncFunction('MayDrag');
if (!mayDrag) {
event.preventDefault();
return false;
}
return true;
};
htmlElement.addEventListener('dragstart', onDragStart);
}
The browser doesn't seem to wait for the async function to complete, and so immediately enables dragging. I want to be able to determine whether drag should be permitted as a result of an async call. Is there a way to achieve this?

Event handlers are synchronous, and need to be, to support bubbling and capturing sanely.
You should eagerly cache the eligibility of dragging and attempt to access that result in your handler instead.
let isDraggable = false
getIsDraggable('maydrag').then((answer) => {
isDraggable = answer
})
const onDragStart = (event) => {
if (!isDraggable) {
event.preventDefault();
}
return isDraggable
}

Related

Callback passed to debounce method not called

Part of a small project I'm working on is the user being able to add tags to items, similarly to StackOveflow. These tags are stored in a database, so obviously I need to call an API to fetch matching tags. To prevent my API from being hit too often, I want to add a debounce method, however none of the examples I've found seem to work. Even lodash's debounce method doesn't work.
I'm currently trying this debounce method in Vue3;
const debounce = (fn: Function, delay: number): Function => {
let timeout: NodeJS.Timeout;
return (...args: any): void => {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
};
onMounted(() => {
debounce(() => console.log('hello'), 500);
});
The debounce method itself is called just fine, but the provided callback isn't. Same goes for lodash's debounce method, the method itself is called but whatever method I pass isn't.
Am I missing something obvious?
Edit: I was indeed missing something obvious. The actual use case was this method (no API call yet, wanted to get the debounce method working first);
const handleTagKeyUp = (event: KeyboardEvent): void => {
filteredTags.value = [];
const value = inputTags.value.trim();
if (event.code === 'Enter') {
addTag(value);
return;
}
if (value.length < 3) {
return;
}
const selectedTagNames = selectedTags.value.map((t: Tag) => t.name.toLowerCase());
filteredTags.value = props.tags.filter((t: Tag) => t.name.toLowerCase().includes(value) && ! selectedTagNames.includes(t.name.toLowerCase()));
};
which is called whenever the keyup event is fired. Simply changing it to
const handleTagKeyUp = (event: KeyboardEvent): void => {
filteredTags.value = [];
const value = inputTags.value.trim();
if (event.code === 'Enter') {
addTag(value);
return;
}
if (value.length < 3) {
return;
}
findTags(value);
};
const findTags = debounce((value: string) => {
const selectedTagNames = selectedTags.value.map((t: Tag) => t.name.toLowerCase());
filteredTags.value = props.tags.filter((t: Tag) => t.name.toLowerCase().includes(value) && ! selectedTagNames.includes(t.name.toLowerCase()));
}, 500);
fixed the issue.
This isn't how debounce is used. Think about it: how would debounce respond to multiple calls to onMounted if a new debounce is created every time onMounted is called?
debounce returns a function that must be called, and calls to that returned function are denounced:
// In some scope. I don't know Vue.js
const debounced = _.debounce(() => console.log('hello'), 500);
onMounted(() => {
debounced()
});

Listener keeps executing in false if statement

I am writing a Chrome extension which has a content script that executes certain actions on seclectionchange event, but it is desired for these actions to execute only when the user has enabled them in the extensions settings.
Here is my code:
const readOption = async(key) => {
return new Promise((resolve, reject) => {
chrome.storage.sync.get([key], function(result) {
if (result[key] === undefined) {
reject();
} else {
resolve(result[key]);
}
});
});
};
async function myAsyncFunction() {
let checkVariable = await readOption('mykey');
if (checkVariable === true) {
document.addEventListener('selectionchange', function(e) {
// Doing stuff
}, false);
}
}
myAsyncFunction();
chrome.storage.onChanged.addListener((changes, area) => {
if (area === 'sync' && changes.mykey) {
myAsyncFunction();
}
});
And here is the problem. When I change the variable from true to false as I can see by setting breakpoints the if (checkVariable === true) gets executed triggered by the onChanged listener and then its body gets skipped, because the variable is false.
But after that the selectionchange executes as if it is not in the if statement. I have to reload the page to prevent its execution.
How do I solve this issue? I thought of removing the selectionchange listener in the onchanged listener only when the mykey is set to false, but how can I do it?
When you register an event listener without specifying {once: true}, it will be active forever in this page until you unregister it explicitly using removeEventListener with the same function reference. To ensure sameness you can simply move the function to the outer scope where it'll be persistent:
async function myAsyncFunction() {
let checkVariable = await readOption('mykey');
let onoff = `${checkVariable ? 'add' : 'remove'}EventListener`;
document[onoff]('selectionchange', onSelChange);
}
function onSelChange(e) {
// Doing stuff
}

Can not remove firestore listener inside a function

My code sample looks like following:
useEffect(() => {
specialfunction();//listener for chat operations
}, []);
const specialfunction = async() => {
var mylistener = firebase.firestore()...onSnapshot((snapshot) => {
//do something with the docs retrieved
});
//my unlucky try to remove the listener after I leave the screen
return () => {
try{
mylistener();
console.log("LISTENER REMOVED")
}catch(error){console.log(error)};
}
};
Usually, if you want to remove a firestore listener, you just call the variable you attached it to, in my example 'mylistener()'.
Unluckily, my listener is not stopping. After I leave the screen and reenter it multiple times and receive a document, I notice that my listener fires multiple times.
I also can not stop it outside my 'specialfunction' because it is not a database listener where I just can call the ref again and stop it, its about a firestore listener here.
I also can not put the 'mylistener' outside of the 'specialfunction', it needs to stay inside.
Can you help me to stop the 'mylistener' after I leave the screen?
Not particularly well-versed in firebase but if you wished to remove event listener when dealing with useEffect hook, the template should be as below
useEffect(() => {
window.addEventListener("keyup", handleKeyUp)
return () => window.removeEventListener("keyup", handleKeyUp)
}, [collapsed, handleKeyUp])
Notice that in your useEffect hook you need to return a function which remove event listener
The key to this solution was another post but under a different topic.
Important to understand was that you need to predefine a variable and after that overwrite it with the firestore listener.
In this case you will be able to detach the listener in the return part of the 'useEffect'-Hook once you leave the screen, code looks like this now:
let mylistener;
useEffect(() => {
specialfunction();//listener for chat operations
return () => {
try{
mylistener(); //here you need to remove the listener
console.log("LISTENER REMOVED")
}catch(error){console.log(error)};
}
}, []);
const specialfunction = async() => {
var mylistener = firebase.firestore()...onSnapshot((snapshot) => {
//do something with the docs retrieved
});
};
Also, 'specialfunction' doesnt need to be async but I do async calls inside this function, its up to you.

setTimeout function in react causes type error

I have a form with a handle function attached to it.
The handle function has a timeout and this is causing some problems.
const timeOut = useRef(null);
const handleSearchChange = (e) => {
// setSearchKey(e.target.value.toLowerCase().trim());
clearTimeout(timeOut.current);
timeOut.current = setTimeout(() => {
setSearchKey(e.target.value.toLowerCase().trim());
}, 500);
}
If I console.log(e.target.value) outside the settimeout function it works fine, when i incorporate the setTimeout function it breaks. Why is this?
I tried simplifying the function to just this :
const handleSearchChange = (e) => {
// setSearchKey(e.target.value.toLowerCase().trim());
console.log(e.target.value)
setTimeout(() => {
// setSearchKey(e.target.value.toLowerCase().trim());
console.log(e.target.value)
}, 500);
}
The issue stays..It logs the first console.log and at the second it breaks.
Event values are cleared by react. You either need to use event.persist to persit event values or store the values from event to be used later
According to react documentation:
SyntheticEvent object will be reused and all properties will be
nullified after the event callback has been invoked. This is for
performance reasons. As such, you cannot access the event in an
asynchronous way.
const handleSearchChange = (e) => {
// setSearchKey(e.target.value.toLowerCase().trim());
clearTimeout(timeOut.current);
const value = e.target.value.toLowerCase().trim();
timeOut.current = setTimeout(() => {
setSearchKey(value);
}, 500);
}
That’s because the e event object in react is a synthetic event object produced by react, not the native event object produced by browser internal.
In order to prevent allocation of new objects all the time, it’s designed to be a reusable object, which means its properties are stripped after emission and re-assigned for next event.
So for your case, because you revisited this object in async callback after emission, it’s been "recycled", making it’s properties outdated. To solve this problem, you can save up beforehand the desired value in the sync event loop, then pass it to async callback.
handleSearchChange = (e) => {
const value = e.target.value.toLowerCase().trim()
clearTimeout(timeOut.current);
timeOut.current = setTimeout(() => {
setSearchKey(value);
}, 500);
}

Is there a shorter way to provide an Observable onComplete handler?

I'm working with an Observable chain and I'm not interested in the next or error values, only when the chain has fully complete. It feels wrong to tap into the subscribe method and supply 2 noops for next and error just so I can provide an onComplete callback.
For Example:
let isRunning = true;
Observable.from([1000, 2000, 3000])
.concatMap(value => Observable.timer(value)
.subscribe(
() => {},
() => {},
() => isRunning = false;
);
What I'd like is something that looks like this
let isRunning = true;
Observable.from([1000, 2000, 3000])
.concatMap(value => Observable.timer(value)
.onComplete(() => isRunning = false);
No, there's no shortcut. However, you can pass null instead of empty functions.
obs$.subscribe(null, null, () => this.isRunning = false);
If you want some kind of trick you could also do
obs$.last().subscribe(() => this.isRunning = false);
This would make sense if, for example, you need access to the last emitted value in the completion handler.
Shortest form would be use observer object.
obs.subscribe({ complete: () => isRunning = false });

Categories

Resources