How to make the 'wheel' event fire once per scroll? - reactjs

I'm trying to write some React code that detects if a user is scrolling up or down and then increments or decrements a state depending on the direction. I'm using the 'wheel' event because I have a 1 viewport sized page that has the 'overflow: hidden' property on it, so there's no where to actually scroll. Instead, I detect the page to show based on the state mentioned earlier.
Here's what I have so far, but this fires multiple times per scroll instead of just once:
useEffect(() => {
const handleTrackpadSwipe = (event) => {
if (event.deltaY > 0) {
console.log('swiped down')
setTrackpadSwipe('swiped down');
if(pageNumber < 4 ) {
setPageNumber(pageNumber + 1);
}
setTrackpadSwipe('none');
}
else if (event.deltaY < 0) {
console.log('swiped up')
setTrackpadSwipe('swiped up');
if(pageNumber > 1) {
setPageNumber(pageNumber - 1);
}
setTrackpadSwipe('none');
}
};
window.addEventListener('wheel', handleTrackpadSwipe);
}, [trackpadSwipe, pageNumber]);

Related

Arraylist doesn't get refilled and/or filtered

I have a list in angular, an array. OnInit it gets filled from the right corresponding database items. I created a form above it. When you enter something in the form, it acts like a filter. This works, the first time. When you erase something from the form and enter something else, the list should be refreshed and afterwards filtered based on the new input. This doesn't happen. I put the formula that happens on onInit in my function to refill the list.
Below you can find my function (I left the console logs in) and a screenshot of the problem. First I look for a user (joeri.boons#hi10.be) which returns three results. Than I erase the user and look based on a month 7. The screen returns a new unfilterd list while in the console it still holds the list of 3 x user joeri.boons#hi10.be. So there is an inconsistency to. If you look at screen result you would think of a filter problem, the console points at a refreshproblem.
if more code is required let me know.
updateWithFilter(): void {
console.log("function update filter reached")
console.log(this.listadapted);
if(this.listadapted == true){
// this.timesheetsHandled = {} as TimeSheet[];
this.getHandledSheet();
console.log("getHandledSheet executed")
}
if(this.filterUsername.trim() && !this.filterYear && !this.filterMonth){
console.log("option 1 reached")
console.log(this.filterUsername.trim());
console.log(this.filterYear);
console.log(this.filterMonth);
this.timesheetsHandled = this.timesheetsHandled.filter(sheet => sheet.username == this.filterUsername);
this.listadapted = true;
} else if(!this.filterUsername.trim() && !this.filterYear && this.filterMonth){
console.log("option 2 reached");
console.log(this.filterUsername.trim());
console.log(this.filterYear);
console.log(this.filterMonth);
console.log("before filter");
this.timesheetsHandled.forEach(sheet => console.log(sheet.username));
this.timesheetsHandled = this.timesheetsHandled.filter(sheet => sheet.month == this.filterMonth);
console.log("after filter");
this.timesheetsHandled.forEach(sheet => console.log(sheet.username));
// console.log(this.timesheetsHandled.filter(sheet => sheet.month == this.filterMonth));
this.listadapted = true;
} else if .. more options
}
ngOnInit(): void {
this.getHandledSheet();
}
getHandledSheet(): void {
this.timesheetService.getAllTimesheets().subscribe({next: (response: TimeSheet[]) => {this.timesheetsHandled = response.filter(sheet => sheet.status == 'HANDLED') }}) ;
}
My guess would be that this is caused by loading data in ngOnInit. As the documentation (https://angular.io/api/core/OnInit) states : [...] It is invoked only once when the directive is instantiated.
I suspect that you create one instance and re-use it and the ngOnInit method does not get called again.
UPDATE:
The issue is that the call to this.getHandledSheet(); does a call to .. .subscribe({next: .. which is delayed and the rest of the function is executed first.
So the actual code after next: is only executed after the timeSheetService is done loading the data.
So either you apply the filter in the
{next: (response: TimeSheet[]) => {this.timesheetsHandled = response.filter(sheet => sheet.status == 'HANDLED') }}
block after filtering for 'HANDLED' or you'll try to await in the update function.
Create two variables, one that will always remain unfiltered, then another that will be filtered.
The problem will be that the original list is filtered, hence you are losing the original data after filtering!
timesheetHandled: TimeSheet[];
timesheetHandledOriginal: TimeSheet[];
updateWithFilter(): void {
console.log('function update filter reached');
console.log(this.listadapted);
if (this.listadapted == true) {
// this.timesheetsHandled = {} as TimeSheet[];
this.getHandledSheet();
console.log('getHandledSheet executed');
}
if (this.filterUsername.trim() && !this.filterYear && !this.filterMonth) {
console.log('option 1 reached');
console.log(this.filterUsername.trim());
console.log(this.filterYear);
console.log(this.filterMonth);
this.timesheetsHandled = this.timesheetHandledOriginal.filter(
sheet => sheet.username == this.filterUsername
);
this.listadapted = true;
} else if (!this.filterUsername.trim() && !this.filterYear && this.filterMonth) {
console.log('option 2 reached');
console.log(this.filterUsername.trim());
console.log(this.filterYear);
console.log(this.filterMonth);
console.log('before filter');
this.timesheetsHandled.forEach(sheet => console.log(sheet.username));
this.timesheetsHandled = this.timesheetHandledOriginal.filter(
sheet => sheet.month == this.filterMonth
);
console.log('after filter');
this.timesheetsHandled.forEach(sheet => console.log(sheet.username));
// console.log(this.timesheetsHandled.filter(sheet => sheet.month == this.filterMonth));
this.listadapted = true;
}
// else if .. more options
}
ngOnInit(): void {
this.getHandledSheet();
}
getHandledSheet(): void {
this.timesheetService.getAllTimesheets().subscribe({
next: (response: TimeSheet[]) => {
this.timesheetsHandled = response.filter(sheet => sheet.status == 'HANDLED');
this.timesheetHandledOriginal = JSON.parse(JSON.stringify(this.timesheetsHandled));
},
});
}

React Native - Resetting setInterval duration when array.length.current == 0 or when array becomes empty

I have a function that adds numbers to an array and then plays the sounds of the numbers and then removes the numbers after they are played at an interval called 'delay'. However, I have trouble figuring out where to reset the delay back to 0. That is - when all the numbers are played and removed from the array. The whole point of this is so that the first number being added will always be played immediately (at 0 second delay), while numbers being added afterwards will be played and removed at an interval like 4 seconds. I have spent a lot of time on solving this problem, but can anyone figure out where to reset the delay back to 0 so that when the array is empty, the first number being added will always be played immediately? Remember that if you keep adding numbers, the numbers will wait at an interval like 4 seconds to being played, like a sound queue.
const [arr, setArr] = React.useState([]); //Sound queue array
const interval = React.useRef(null);
const arr_length = React.useRef(null);
const [delay, setDelay] = useState(0);
function set_remove_arr_interval(num) {
setArr((currentNumbers) => [...currentNumbers, num]);
if (!interval.current) {
interval.current = setInterval(() => {
// Sets an ongoing interval for the sound queue
console.log("Array current position: " + arr_length.current);
if (arr_length.current > 0) {
playSound(arr[0][0], arr[0][1], arr[0][2]);
setDelay(4000); //My delay duration at 4 seconds
}
if (arr_length.current === 0) {
// setDelay(0); // <-- I tried resetting the delay duration here to 0 but it resets to 0 every time a number is entered immediately and plays it, which is not what I want.
clearInterval(interval.current);
interval.current = null;
return;
}
arr_length.current = arr_length.current - 1;
setArr((currentNumbers) => [...currentNumbers.slice(1)]);
}, delay);
}
}
function add(num) {
arr_length.current = arr.length + 1;
set_remove_arr_interval(num);
}
Edit: I also tried initialising a new variable called isFirst, but it only works at alternating times of the interval:
const [isFirst, setIsFirst] = useState(true);
if (isFirst == false && arr_length.current == 0) {
setDelay(0);
setIsFirst(true);
}
if (arr_length.current > 0) {
playSound(arr[0][0], arr[0][1], arr[0][2]);
if (isFirst == true) {
setDelay(4000);
setIsFirst(false);
}
}
I now solved the problem by simply resetting the delay back to 0 using the useState hook when the array's length is at 1. Does anybody have a better solution than this? Because I believe hooks are not encouraged to be used inside functions and I also found out that sometimes console.logging them does not log the new state when changed.
sound.setOnPlaybackStatusUpdate(async (status) => {
if (status.didJustFinish === true) {
const { sound } = await Audio.Sound.createAsync(data[0], {
shouldPlay: true,
});
setSound(sound);
await sound.playAsync();
if (arr.length == 1) {
setDelay(0);
}
}
});

How can I open a markercluster programmatically

I want to be able to click on a list and make the map flyTo the position of a marker and open its popup. I'm able to do so as long as the marker has an identical position (not spiderfied). I've made a script to find the markerCluster which contains the marker, but I can't trigger its click method, to make the marker accessable.
// if marker is not accessible
Object.values(mapRef.current._targets).forEach(_target => {
if (_target._markers) {
const wantedMarker = _target._markers.find(_marker => {
return (
_marker.options.id === someId
);
});
if (wantedMarker) {
_target.click() // _target.click() is not a function
Without a living code example, I can't confirm that wantedMarker is indeed the instance of the marker you want. But if its, .click() is indeed not a function on that. However, you can get the marker's popup, if there is one, and open that programatically:
const popup = wantedMarker.getPopup();
if (popup){
popup.openOn(map) // you'll need a reference to the L.map instance for this
}
Solved it by looking for "_icon" in all parent elements. Elements hidden by markerCluster does not have this property. The _icon element has click events, so I used that to open the marker clusters.
const openMarkerClustersAndMarkerPopup = (allMarkersRef, markerId) => {
const markerElementRef = allMarkersRef.current[markerId];
if (markerElementRef._icon) {
markerElementRef._icon.click();
} else {
const clusters = [];
let _currentElement = markerElementRef;
while (_currentElement.__parent) {
clusters.push(_currentElement);
if (_currentElement._icon) {
break;
}
_currentElement = _currentElement.__parent;
}
// Trigger markercluster and marker by starting with top markercluster
for (let i = clusters.length - 1; i >= 0; i--) {
const _icon = clusters[i]._icon;
_icon && _icon.click();
}
}
};

How to dynamically hover over react-list elements when arrow keys are pressed?

I'm using react-select and I'm rendering the Control component as null, because I already have my own text input for the user search. With my custom input, the null Control component, and the rest of the react-select components, my drop down looks like this:
The issue is that I cannot use the up and down arrow keys to navigate between results.
I've decided to verify that this issue is because of the nullified Control component, however after rendering the default input field, the up and down arrows still do not work, as they do in their demos in the docs.
I've also tried setting defaultValue={!!searchResults.length && searchResults[0]}, but that does not work either.
What can I do here?
Here is how I ended up resolving this issue:
export const addEventListenerArrowUpAndDown = () => {
document.addEventListener("keydown", e => {
// Make sure the drop down is open. In my case, it's shown only
// when the length of my searchResults array is not 0
const searchResultsExist = !!store.getState().search.searchResults.length;
if (searchResultsExist) {
// Pull all elements with a common class name from the drop down (all elements
// usually have only this common class, the rest is too dynamic and unreliable)
const searchResults = Array.from(
document.getElementsByClassName("MuiListItem-root")
);
// I initially tried using .indexOf, but that didn't work, so I'm using this way
let currentlyPseudoHoveredElement = -1;
// See if one of the items already are "hovered" over
searchResults.forEach((element, index) => {
if (element.style.background === "rgba(85, 125, 146, 0.5)") {
currentlyPseudoHoveredElement = index;
}
});
// On Arrow Key down
if (e.keyCode === 40 && !!searchResults.length) {
if (
currentlyPseudoHoveredElement !== -1 &&
currentlyPseudoHoveredElement !== 4
) {
// Set current background color to preferred color
searchResults[currentlyPseudoHoveredElement].style.background =
"#FFFFFF";
// Set next element's background color to preferred color
searchResults[currentlyPseudoHoveredElement + 1].style.background =
"rgba(85, 125, 146, 0.5)";
} else {
searchResults[0].style.background = "rgba(85, 125, 146, 0.5)";
}
}
// On Arrow Key down
else if (e.keyCode === 38) {
if (
currentlyPseudoHoveredElement !== -1 &&
currentlyPseudoHoveredElement !== 0
) {
// Set current background color to white
searchResults[currentlyPseudoHoveredElement].style.background =
"#FFFFFF";
// Set previous element's background color to preferred color
searchResults[currentlyPseudoHoveredElement - 1].style.background =
"rgba(85, 125, 146, 0.5)";
} else {
searchResults[0].style.background = "rgba(85, 125, 146, 0.5)";
}
}
}
});
};
export const addEventListenerOnEnterOpenPseudoHoveredElement = () => {
document.addEventListener("keydown", e => {
if (e.keyCode === 13) {
const searchResults = Array.from(
document.getElementsByClassName("MuiListItem-root")
);
// In the event that the drop down is shown and one of the
// elements is "hovered" over, open that URL that it points to
// This also works if I use the actual mouse to hover over something; it will open the link this way
searchResults.forEach((element, index) => {
if (element.style.background === "rgba(85, 125, 146, 0.5)") {
element.click();
}
});
}
});
};

Unsubscribe from timer in rxjs

Hi I have a timer running which is like it should show a component for 30sec after every 10 seconds. My code is like this`
import { timer } from "rxjs";
componentWillReceiveProps(nextProps, nextState) {
console.log("RECEIVED PROPS");
if (this.props.venuesBonusOfferDuration === 0) {
this.subscribe.unsubscribe();
}
this.firstTimerSubscription;
this.secondTimerSubscription;
// if (this.state.isMounted) {
if (
nextProps.showBonusObj &&
(!nextProps.exitVenue.exitVenueSuccess || nextProps.enterVenues)
) {
// console.log("isMounted" + this.state.isMounted);
//if (this.state.isMounted) {
let milliseconds = nextProps.venuesBonusOfferDuration * 1000;
this.source = timer(milliseconds);
this.firstTimerSubscription = this.source.subscribe(val => {
console.log("hiding bonus offer");
this.firstTimerSubscription.unsubscribe();
this.props.hideBonusOffer();
this.secondTimerSubscription = bonusApiTimer.subscribe(val => {
console.log("caling timer" + val);
this.props.getVenuesBonusOffer(
this.props.venues.venues.id,
this.props.uid,
this.props.accessToken
);
});
});
//}
} else {
try {
if (this.secondTimerSubscription != undefined) {
this.secondTimerSubscription.unsubscribe();
console.log("secondTimer UNSUBSCRIBED");
}
if (this.firstTimerSubscription != undefined) {
this.firstTimerSubscription.unsubscribe();
console.log("firstTimer UNSUBSCRIBED");
}
} catch (error) {
console.log(
"error when removing bonusoffer timer" + JSON.stringify(error)
);
}
//}
}
}
`
Problem is if I try to unsubscribe this * this.firstTimerSubscription* and this.secondTimerSubscription like this
try {
if (this.secondTimerSubscription != undefined) {
this.secondTimerSubscription.unsubscribe();
console.log("secondTimerunmount UNSUBSCRIBED");
}
if (this.firstTimerSubscription != undefined) {
this.firstTimerSubscription.unsubscribe();
console.log("firstTimerunmount UNSUBSCRIBED");
}
} catch (error) {
console.log("error bonusoffer timer" + JSON.stringify(error));
}
its still prints logs within timer like "hiding bonus offer" and "calling timer".
Can someone please point out the issue. It been a day since am into this.
Any help is appreciated.
The problem is that you subscribe multiple times (whenever component receives props) and reassign newest subscription to firstTimerSubscription or secondTimerSubscription references. But doing that, subscriptions does not magically vanish. To see how it works here is a demo:
const source = timer(1000, 1000);
let subscribe;
subscribe = source.subscribe(val => console.log(val));
subscribe = source.subscribe(val => console.log(val));
setTimeout(() => {
subscribe.unsubscribe();
}, 2000)
Even though you unsubscribed, the first subscription keeps emiting. And the problem is that you lost a reference to it, so you can't unsubscribe now.
Easy fix could be to check whether you already subscribed and unsubscribe if so, before subscribing:
this.firstTimerSubscription ? this.firstTimerSubscription.unsubscribe: false;
this.firstTimerSubscription = this.source.subscribe(...
I wouldn't use a second timer. Just do a interval of 10 seconds. The interval emits the iteration number 1, 2, 3..... You can use the modulo operator on that tick. Following example code (for example with 1 second interval) prints true and false in console. After true it needs 3 seconds to show false. After false it needs 1 second to show true.
interval(1000).pipe(
map(tick => tick % 4 !== 0),
distinctUntilChanged(),
).subscribe(x => console.log(x));

Resources