Modifying classList in event handler in React component - javascript

What I am trying to achieve with this is quite simple. I have an array of options/tags displayed in a div. I want the classes to change on click to simulate an "active" state. While I've seen a few snippets on codesandbox that I could use to achieve what I want, my curiosity still is why this wouldn't even trigger the class change.
The referenced event elements and id match...
const variantHandler = (e: any) => {
e.preventDefault()
const d = e.currentTarget
const c = () => {
variants.map((variant: any) => {
if (d.id !== variant.node.id) {
d.classList.remove('bg-gray-900', 'text-myGray', 'dark:bg-myGray', 'dark:text-gray-900')
//alert('reached remove')
}
else {
d.classList.add('bg-gray-900', 'text-myGray', 'dark:bg-myGray', 'dark:text-gray-900')
//alert('reached add')
}
})
}
c();
}

Related

"converting" an anon function to class method with event data input

I've obtained an anonymous function that takes an event object as input as well as another input like so:
// define a state toggle function
const toggleDrawer = (open) => (event) => {
// quit for certain key events
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
} // if
// set the given toggle open state
setState(open);
};
I would like to incorporate this as a method in a class, but unsure how to rewrite inputs.
I could just store the variable as a property on the object, but I would like to write it like so:
/*
View-model of the Drawer view.
*/
class DrawerModel {
// class constructor
constructor() {
// initialise a drawer state
const [open, setOpen] = React.useState(false);
// store the state and set function on the object
this.open = open;
this.setOpen = setOpen;
} // constructor
// method to toggle the drawer
toggleDrawer(open) {
// quit for certain key events
// TODO: how to pass event in like with the anon fnc?
// const toggleDrawer = (open) => (event) => {...
//if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
// return;
//} // if
// set the given toggle open state
setState(open);
} // toggleDrawer
} // class
The toggleDrawer function can be assigned as the callback for some UI component events.
I don't understand the anonymous function definition with multiple inputs "= (open) => (event) =>".
Could someone explain/link how that works?
How do I make that event data available with how I'm writing the class method?
It is currying function. It is function which returns function. With classic syntax (not arrow syntax as your example is) you can write it like this:
class DrawerModel {
toggleDrawer(open) {
return function(event) {
// do your magic here
}
}
}

use of lodash debounce function

I am using a debounce function in my react native project. It gets triggered after user enters 3 characters but it is triggering after 2 sec instead of the delay i assigned to it.
constructor() {
this.init()
this.hitSearchApi = debounce(() => {
log('inside if is called debounce')
this.getStudentTableData(() => this.updateFetchingStatus(true))
this.updateSearchLoaderStatus(false)
this.updateFetchingStatus(false)
}, 100)
}
I had created my debounce function inside the store constructor like this.
#action
onChangeSearchText = (searchText) => {
if (get(searchText, 'length', 0) > 2) {
log('inside search text if condition')
this.updateSearchLoaderStatus(true)
this.hitSearchApi.cancel()
// this.updateSearchLoaderStatus(true)
log('before hit search api')
this.hitSearchApi()
// fetch student list on search basis
} else if (get(searchText, 'length') === 0) {
this.setStudentListData({})
this.updateFetchingStatus(true)
this.resetSearchData()
}
this.searchText = searchText
}
This is my onChange text triggered, which is getting called on textinput onChange event.
Please help me out in this. What am I doing wrong here?

useCallback returns n+1 times where n is the number of state variables

I'm trying to set up a custom context menu, however whenever the user right clicks the context menu function returns 6 separate times, the 5th being what I need and the 6th being the default state values. However if the user double right-clicks in the same spot it returns 5 times, with the 5th return being the desired values and the menu opens. Is there a way to check before the return if all the states are changed and only return from the callback if all the needed information is present?
const ContextMenu = outerRef => {
const [xPos, setXPos] = useState("0px");
const [yPos, setYPos] = useState("0px");
const [menu, showMenu] = useState(false);
const [menuTarget, setMenuTarget] = useState('');
const [menuTargetId, setMenuTargetId] = useState('');
const handleContextMenu = useCallback(
event => {
if(event.target.className && (event.target.className.includes('bar') ||event.target.className == 'timeline' || event.target.className == 'draggablediv' || event.target.className == 'editableDiv')){
event.preventDefault();
if (outerRef && outerRef.current.contains(event.target)) {
setXPos(`${event.pageX}px`);
setYPos(`${event.pageY}px`);
setMenuTarget(event.target.className)
setMenuTargetId(event.target.id)
showMenu(true);
} else {
showMenu(false);
}
}
},[showMenu, outerRef, setXPos, setYPos]);
const handleClick = useCallback(() => {
showMenu(false);
}, [showMenu]);
useEffect(() => {
document.addEventListener("click", handleClick);
document.addEventListener("contextmenu", handleContextMenu);
return () => {
document.removeEventListener("click", handleClick);
document.removeEventListener("contextmenu", handleContextMenu);
};
}, []);
return {xPos, yPos, menu, menuTarget, menuTargetId};
};
useCallback accepts a function that returns the memoized value. Like useCallback(() => 5, []), or in your case useCallback(() => event => {...}, []).
This is because React has to call the function to get the value to memoize, so your setters are being called on every render. Which is what's causing all the weirdness.
However, even with that fix I don't think it's correct to use a function that changes references in addEventListener. You will never have up-to-date values in your contextmenu handler because it will refer to an old version of handleContextMenu. You will probably have to use a more idiomatic way of attaching a function to a UI event than the global document api.

querySelectorAll and multiple elements with the same class

I am currently trying to adapt this demo for page transitions when you click on the links with the same class applied to them.
I am not sure how to adapt the following piece of code for all the elements that has the same class following the use of querySelectorAll. What do you think should be tweaked to make it work with querySelectorAll and multiple elements with the same class?
(function() {
const elmHamburger = document.querySelector('.link-with-overlay');
const elmOverlay = document.querySelector('.shape-overlays');
const overlay = new ShapeOverlays(elmOverlay);
elmHamburger.addEventListener('click', () => {
if (overlay.isAnimating) {
return false;
}
overlay.toggle();
if (overlay.isOpened === true) {
elmHamburger.classList.add('is-opened-navi');
} else {
elmHamburger.classList.remove('is-opened-navi');
}
Thanks!
To allow all the links to have an onclick event you'll need to iterate over the NodeList that the querySelectorAll method returns.
NOTE: You cannot do NodeList.forEach in IE11 so you'd need to polyfill or convert it to a true JS array before iterating.
(function() {
const elmHamburgers = document.querySelectorAll('.link-with-overlay');
const elmOverlay = document.querySelector('.shape-overlays');
const overlay = new ShapeOverlays(elmOverlay);
const onHamburgerClick = function() {
if (overlay.isAnimating) {
return false;
}
overlay.toggle();
if (overlay.isOpened === true) {
this.classList.add('is-opened-navi');
} else {
this.classList.remove('is-opened-navi');
}
};
// Iterates over all of the elements matched with class .link-with-overlay and
// adds an onclick event listener
elmHamburgers.forEach(elem => elem.addEventListener('click', onHamburgerClick));
})();
You could also replace the conditional: if (overlay.isOpened === true) {...
with a one liner using this.classList
this.classList.toggle('is-opened-navi', overlay.isOpened)

Angular2 Custom Events

I'm trying to find out how to handle custom DOM events emitted by something outside of Angular, for example the following:
document.querySelector('my-custom-element').dispatchEvent(new Event('my.customEvent'));
So far I have tried to register a new EventManagerPlugin that supports everything starting with 'my.' but if I print out all events that come by all 'normal' event like 'click' and 'submit' are printed out; but none of my custom events.
html:
<my-custom-element (my.customEvent)="handleCustomEvent($event)"></my-custom-element>
ts:
supports(eventName: string):boolean {
var ret = false;
if (eventName.indexOf('my.') === 0) {
ret = true;
}
console.log('supports event?', eventName, ret);
return ret;
}
The console.log line only prints native events and ng*events but not my custom event :(
EDIT Fixed solution
I've moved the (my.customEvent) inside the component annd the log showed the custom event.
Binding an external event to the angular2 internal event while seperating the 2 is fixed by using a custom eventHandler in the EventManagerPlugin
Relevate code
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
let zone = this.manager.getZone();
// Entering back into angular to trigger changeDetection
var outsideHandler = (event: any) => {
zone.run(() => handler(event));
};
// Executed outside of angular so that change detection is not constantly triggered.
var addAndRemoveHostListenersForOutsideEvents = () => {
this.manager.addEventListener(element, 'external.' + eventName, outsideHandler);
}
return this.manager.getZone().runOutsideAngular(addAndRemoveHostListenersForOutsideEvents);
}
Trigger the event via DOM:
document.querySelector('my-custom-element').dispatchEvent(new Event('external.my.customEvent'));
Now you can trigger an event from the DOM which is pushed into angular2 world and can the code is handled from within the component.
Try to extend the DomEventsPlugin, for example:
import {DomEventsPlugin} from 'angular2/platform/common_dom';
// Have to pull DOM from src because platform/common_dom returns DOM as null.
// I believe its a TS bug.
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {Injectable} from 'angular2/core';
import {noop} from 'angular2/src/facade/lang';
#Injectable()
export class DOMOutsideEventPlugin extends DomEventsPlugin {
eventMap: Object = {
"clickOutside": "click",
"mousedownOutside": "mousedown",
"mouseupOutside": "mouseup",
"mousemoveOutside": "mousemove"
}
supports(eventName: string): boolean {
return this.eventMap.hasOwnProperty(eventName);
}
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
var zone = this.manager.getZone();
var documentEvent = this.eventMap[eventName];
// Entering back into angular to trigger changeDetection
var outsideHandler = (event) => {
zone.run(() => handler(event))
};
// Executed outside of angular so that change detection is not constantly triggered.
var addAndRemoveHostListenersForOutsideEvents = () => {
DOM.onAndCancel(DOM.getGlobalEventTarget('document'), documentEvent,
(event) => {
let current = event.target;
// if the element/event is propagating from the element its bound to, don't handle it.
if (current.parentNode && current !== element) {
outsideHandler(event);
}
});
}
return this.manager.getZone().runOutsideAngular(addAndRemoveHostListenersForOutsideEvents);
}
addGlobalEventListener(target: string, eventName: string, handler: Function): Function {
var element = DOM.getGlobalEventTarget(target);
var zone = this.manager.getZone();
var outsideHandler = (event) => zone.run(() => handler(event));
if ((target === "document") || (target === "window" )) {
return noop;
}
return this.manager.getZone().runOutsideAngular(
() => DOM.onAndCancel(element, eventName, outsideHandler)
);
}
}
source: https://medium.com/#TheLarkInn/creating-custom-dom-events-in-angular2-f326d348dc8b#.so0jvssnz

Categories