RxJS cancel overlapping events and delay - javascript

How to delay 300ms and take only last event from mouseLeave event stream Observable in RxJS? I wanted to take latest event only and delay it to 300 milli seconds then fire a method.
class MouseOverComponent extends React.Component {
state = {menuIsOpen: false}
componentDidMount() {
this.mouseLeave$ = Rx.Observable.fromEvent(this.mouseDiv, "mouseleave")
.delay(300)
.throttleTime(300)
.subscribe(() => /* here I want to hide the div */);
}
componentWillUnmount() {
this.mouseLeave$.unsubscribe();
}
menuToggle = e => {
e && e.preventDefault()
let {menuIsOpen} = this.state
menuIsOpen = !menuIsOpen
this.setState({menuIsOpen, forceState: true})
}
render() {
// const menuStateClass = ... resolving className with state
return (
<div ref={(ref) => this.mouseDiv = ref}>
Move the mouse and look at the console...
</div>
);
}
}
but its not working its firing previous events also. Its hiding and showing uncontrollable while i do fast mouse leave.
I want mouseDiv when mouse leaves from the div and wait for 300ms then hide.

Add a first() and repeat() will reset your stream from clean state and it probably can solve your issue.
Rx.Observable.fromEvent(block, "mouseleave")
.delay(300)
.throttleTime(300)
.first()
.repeat()
.subscribe(console.log);
fiddle: http://jsfiddle.net/cy0nbs3x/1384/

I think by "take latest event only" you mean you want to get the last value from fromEvent(this.mouseDiv, "mouseleave") when you call this.mouseLeave$.unsubscribe();.
By calling .unsubscribe() you dispose the chain which is not what you want in this case. Instead you can use takeUntil and takeLast(1) operators like the following to complete the chain that triggers takeLast(1) that passes the last value it received:
componentDidMount() {
this.mouseLeave$ = Rx.Observable.fromEvent(this.mouseDiv, "mouseleave")
.takeUntil(this.unsub$)
.takeLast(1)
.delay(300)
.subscribe(() => /* here I want to hide the div */);
}
componentWillUnmount() {
this.unsub$.next();
}

Related

Incorrect focusout trigger when changing focus within container

Suppose I've got the Code structure below. If I focus an element within my red container (one of the inputs), the focusin event triggers. Likewise, if I click outside the red container, the focusout event triggers.
However, if I click one of the input elements, then directly the other, both a focusout and a focusin event get triggered in quick succession.
Is there an easy way to avoid this or find out whether the second focusout event can be ignored because focus in fact stays within the relevant element, aside from ugly solutions like setting a flag on the first focusout event and waiting for a render tick to see whether another focusin event happens?
document.getElementById("el").addEventListener("focusin",
() => document.getElementById("out").innerHTML += "focusin<br/>");
document.getElementById("el").addEventListener("focusout",
() => document.getElementById("out").innerHTML += "focusout<br/>");
<div id="el" style="background-color: red; padding: 4px">
<input />
<input />
</div>
<div id="out">
</div>
As there doesn't seem to be an easy solution, I've wrote the ugly solution of waiting for the next render tick. I wrote it as reusable React hook, so if it helps someone, here it is.
export const useFocusWithin = (
element: HTMLElement | undefined,
onFocusIn?: () => void,
onFocusOut?: () => void,
) => {
const [focusWithin, setFocusWithin] = useState(false);
const isLoosingFocusFlag = useRef(false);
useHtmlElementEventListener(element, 'focusin', () => {
setFocusWithin(true);
onFocusIn?.();
if (isLoosingFocusFlag.current) {
isLoosingFocusFlag.current = false;
}
});
useHtmlElementEventListener(element, 'focusout', (e) => {
isLoosingFocusFlag.current = true;
setTimeout(() => {
if (isLoosingFocusFlag.current) {
onFocusOut?.();
isLoosingFocusFlag.current = false;
setFocusWithin(false);
}
});
});
return focusWithin;
};
export const useHtmlElementEventListener = <K extends keyof HTMLElementEventMap>(
element: HTMLElement | undefined, type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any) => {
useEffect(() => {
if (element) {
element.addEventListener(type, listener as any);
return () => element.removeEventListener(type, listener as any);
}
}, [element, listener, type]);
};

Ag-grid opposite method to cellFocused

folks!
Does anyone know the opposite method to cellFocused in ag-grid?
I need to detect when the focused cell loses its focus and run some actions.
Thanks for your responses.
I've found a way to support onBlur event. Since ag-grid doesn't have a built-in method, I created wy own event listener to the focus cell node and remove it after losing the focus state.
So, my code looks like this. Inside the react class I have 3 additional methods:
removeCellBlurListener = () => {
const target = document.activeElement;
if (target) {
target.removeEventListener('blur', this.onCellBlur);
}
};
addCellBlurListener = () => {
const target = document.activeElement;
if (target) {
target.addEventListener('blur', this.onCellBlur);
}
};
onCellBlur = () => {
...do something on blur
};
render () {
return (
<AgGridReact
{...restProps}
onCellFocused={(e) => this.addCellBlurListener()}
onGridReady={this.onGridReady}
/>
);
}

React Material: progressBar with mouse listener

I've faced with problem using React and React Material-UI components. What I need:
1) User clicks button in my component - I should add mousemove listener to the page and show ProgressBar.
2) User moves mouse - I count events, and update my ProgressBar.
3) When count of events is 50, I remove mousemove listener and hide ProgressBar.
I tried to do this with React useEffect, useState Hooks, but it does not remove listener. I don't understand, why.
Here is my code:
const [completed, setCompleted] = React.useState(0);
const [keyGenState, setKeyGenState] = React.useState(0);
const updateMousePosition = ev => {
console.log("UMP");
setCompleted(old => old + 1);
/*I tried to check completed value here, but it always is 0 - maybe, React re-renders component on setState..
And I decided to take useEffect hook (see below)*/
};
useEffect(() => {
console.log(completed); /*Just to understand, what happens) */
if (completed === 49) {
return () => {
/*When completed value is 50, this log message appears, but mouse listener still works! */
console.log("Finish!");
document.removeEventListener("mousemove", updateMousePosition);
setKeyGenState(2);
}
}
}, [completed]);
function handleChange(e) {
switch (e.currentTarget.id) {
/*startKeyGen - it is ID of my Button. When user clicks it, I show ProgressBar and add mousemove listener*/
case "startKeyGen" : {
setKeyGenState(1);
document.addEventListener("mousemove", updateMousePosition);
break;}
}
}
/*Other logics. And finally, JSX code for my ProgressBar from Material-UI*/
<LinearProgress hidden={keyGenState !== 1 } variant="determinate" value={completed} style={{marginTop: 10}} />
It looks really strange: why React ignores removeEventListener.
Please, explain, where is my mistake.
UPD: Thanks a lot! I used useCallback hook, in this manner:
const updateMousePosition = React.useCallback(
(ev) => {
//console.log("Calback");
console.log(ev.clientX);
setCompleted(old => old + 1);
},
[],
);
useEffect(() => {
//console.log(completed); /*Just to understand, what happens) */
if (completed === 49) {
return () => {
/*When completed value is 50, this log message appears, but mouse listener still works! */
console.log("Finish!");
document.removeEventListener("mousemove", updateMousePosition);
setKeyGenState(2);
}
}
});
But I still don't understand completely.. So, when I used useCallback with empty dependencies array, this function (updateMousePosition), will be unchanged during all "life" of my component? And in useEffect I remove mouseListener. It is magic for me: why does useEffect ignore removing without useCallback?
Try to use React.useCallback for updateMousePosition. Every change in your component creates new function (reference). So React.useEffect remove last updateMousePosition but doesn't remove added in handleChange.

React document.addEventListener is detecting multiple keypresses

I am trying to create a keypress listener for my React Js calculator app and when I add the event listener in, it detects additional key presses the more I press. Is there a better place to put the event listener? When I press 1234, I get
122333344444444
/****************Button Component*************/
class CalcApp extends React.Component {
state = {
value: null,
displayNumbers: '0',
selectedNumbers: [],
calculating: false,
operator:null
};
selectMath = (selectedMath) =>{
const {displayNumbers, operator,value} = this.state;
const nextValue = parseFloat(displayNumbers)
console.log(selectedMath);
/**do math and other methods*/
render() {
document.addEventListener('keydown', (event) => {
const keyName = event.key;
if(/^\d+$/.test(keyName)){
this.selectButton(keyName)
console.log(keyName);
}
});
return (
<div>
<Display displayNumbers={this.state.displayNumbers}
selectedNumbers={this.state.selectedNumbers}/>
<Button selectedNumbers={this.state.selectedNumbers}
selectButton ={this.selectButton}
selectC = {this.selectC}
displayNumbers={this.state.displayNumbers}
selectDot = {this.selectDot}
selectMath = {this.selectMath}/>
</div>
);
}
}
let domContainer = document.querySelector('#app');
ReactDOM.render(<CalcApp />, domContainer);
Remove document.addEventListener listener from render().
The method is being called whenever the components needs to re-render (changes of state / props) which attaches yet another event listener.
Suggestion: Move document.addEventListener to componentDidMount() - executed only once, and remove it via document.removeEventListener on componentWillUnmount to prevent memory leaks.

Calling a method when scroll starts

I have a react component that uses scroll events. When the scroll event is called it basically runs the handler as expected. However I need to be able to call a method once when the scroll events begin to fire. I understand the idea of a debounce method which would fire when the scroll stops, but I need to find a way to fire once when scrolling begins. (For sure NO jQuery can be used).
componentDidMount() {
window.addEventListener('scroll', this.onScroll);
this.setState({
// sets some state which is compared in a shouldComponentUpdate
});
}
componentWillUnmount() {
window.removeEventListener('scroll', this.onScroll);
}
shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
The handler seen below runs some code:
onScroll() {
this.scrollTop = document.documentElement.scrollTop;
this.update();
}
But I need a function that runs once:
scrollStart() {
}
I would love to say I have tried something but unfortunately I have no ideas. Any assist would be greatly appreciated.
There is no real scrolling state in the browser; the scroll event happens, and then it's over.
You could e.g. create a new timeout each time the user scrolls and set your own scrolling state to false if the user hasn't scrolled until the timeout function is run.
Example
class App extends React.Component {
timeout = null;
state = { isScrolling: false };
componentDidMount() {
window.addEventListener("scroll", this.onScroll);
}
componentWillUnmount() {
window.removeEventListener("scroll", this.onScroll);
}
onScroll = () => {
clearTimeout(this.timeout);
const { isScrolling } = this.state;
if (!isScrolling) {
this.setState({ isScrolling: true });
}
this.timeout = setTimeout(() => {
this.setState({ isScrolling: false });
}, 200);
};
render() {
return (
<div
style={{
width: 200,
height: 1000,
background: this.state.isScrolling ? "green" : "red"
}}
/>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Do you want the scrollStart() function to only fire once, the very first time a user scrolls? If do, this could be easily accomplished with a scrollStarted variable. if (scrollStarted == false) { scrollStarted = true; scrollStart(); }.
I can imagine a similar scenario if you want the function to fire when a user starts scrolling from the top (it can fire again if the user returns to the top). Just replace (scrollStarted == false) with scrollTop == 0.
There is no scrollstart event in javascript, however you can register pointer events on the parent element and scroll events on the target element.
Then, for example, in the pointerdown callback reset a variable that gets set when scrolling starts.
If you want you can even dispatch a custom "scrollstart" event on the target when the scroll event is triggered and var scrolling is not set.
For document.body you can listen for pointer ( or touch or mouse ) events on window.
For this you could define a static variable and when the scroll starts, put true in the variable and enter a while that keeps checking if the scrool continues. Using this logic, you may be able to do something.

Categories