I am trying to make application similar to facebook. When I reach bottom of the page I am fetching more data from API. I am using React and Redux.
Simplified code:
const detectBottomOfPage= () => {
if((window.innerHeight + window.scrollY) > document.body.scrollHeight) {
dispatch(bottomOfPage()); //increases scrollCount
}
};
useEffect(() => {
window.addEventListener('scroll', detectBottomOfPage);
}, []);
useEffect(() => {
dispatch(fetchMoreData(scrollCount));
},[scrollCount]);
When i scroll down it works fine, and fetches data. (page increases in height). But when i refresh the page, it detects bottom of page multiple times. I tried to remove listener before refresh (beforeunload event) but it doesn't work properly after refresh. Any solution ?
Instead of fighting with all the different scrolling quirks, the sanest solution is to ratelimit how fast you can trigger the infinite scrolling.
One way to do it would be to update your effect with some state checking:
const [lastFetch, setLastFetch] = React.useState(0);
useEffect(() => {
const now = Math.floor(Date.now()/3000);
if (lastFetch < now) {
dispatch(fetchMoreData(scrollCount));
setLastFetch(now);
}
},[scrollCount]);
Related
I was trying to move user to a certain position (which a HTMLElement with a given id is) when user click a button. So far I have tried using hashtag directly and using hash tag and add a listener listen to the router change in useEffect, and send user to the element if hashtag is detected and that element is not in the view port. They are not working at most of the time. I added a while loop to keep triggering the function, which results bringing user to the element after 20+ calls in 7-8 seconds. That is just annoying.
Does anyone successfully make the hashtag works? How can that be done?
What I have tried so far:
hashtag link: failed
hashtag link with a slash before the hashtag: failed, can't see a difference
function moving user to the location: failed, but the function itself is successfully triggered
What I am observing at the moment:
User is not moved at all
User is moved, but the process suddenly stops before user actually reach that element. I used smooth behavior as my global css, so this effect is really strange to me.
Keep checking if user is not reaching that position, and send them to it if checked. Not working as expected. It does check and send user and send again if another check still fails, but the process is too long (7-8 seconds, or longer) and if user scroll in the process they will still be dragged to the element if they haven't reach it.
Related code:
function isInViewport(el: HTMLElement) {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
useEffect(() => {
const path = router.asPath;
if (path && path.includes('/#')) {
const sectionName = path.replace('/#', '');
const ele = document.getElementById(sectionName);
if (ele) {
const interval = setInterval(() => {
if (!isInViewport(ele)) {
console.log('triggered');
setTimeout(() => {
ele.scrollIntoView({ behavior: "smooth" })
}, 2000)
}
else {
clearInterval(interval);
}
}, 2000);
}
}
}, [router.asPath]);
I tried to use js code to highlight text items on scroll. I tried using js. So the class for changing the element is added immediately after the page is loaded, and not when scrolling to it. Although I checked this js code in a separately created site and everything worked fine
Before that I tried plugins AOS and WOW. But they didn't work because of the ScrollTrigger plugin and something else on the page. How can I make it so that on my site the class is added to the block only when I have scrolled to it and removed when I have scrolled it? How to make friends with the ScrollTrigger plugin with AOC or WOW?
document.addEventListener('DOMContentLoaded', () => {
const scrollItems = document.querySelectorAll('.services-items');
const scrollAnimation = () => {
let windowCenter = (window.innerHeight / 2) + window.scrollY;
console.log(windowCenter)
scrollItems.forEach(el => {
let scrollOffset = el.offsetTop + (el.offsetHeight / 2);
if (windowCenter >= scrollOffset) {
el.classList.add('animation-class');
} else {
el.classList.remove('animation-class');
}
});
};
scrollAnimation();
window.addEventListener('scroll', () => {
scrollAnimation();
});
});
In the React Native app I have implemented dismiss keyboard. However when the keyboard is dismissed from scroll view, there is a delay and a grey area will show up before the keyboard disappears. How can I avoid that from happening?
The grey area looks like the tableViewCell being selected.
To change the selectionStyle in swift it would be: cell.selectionStyle = .blue or .none
The delay is a little more difficult. I would make sure I am running on a device to test it and make sure the function that dismisses the keyboard is being called from the main thread.
I was dealing with a similar issue, ended up setting height on my wrapping view when the keyboardWillHide event fires.
Most basic code looks like:
useEffect(() => {
const showSubscription = Keyboard.addListener("keyboardDidShow", (keyboardEvent) => {
const screenHeight = Dimensions.get("window").height;
const keyboardHeight = keyboardEvent.endCoordinates.height;
setContainerStyle({
height: screenHeight - keyboardHeight,
});
});
const hideSubscription = Keyboard.addListener("keyboardWillHide", () => {
setContainerStyle({
height: "100%",
});
});
return () => {
showSubscription.remove();
hideSubscription.remove();
};
}, []);
Hopefully that helps 😁
I have a React application that is utilizing Material UI. The application has slider components implemented in a lot of places (https://material.io/components/sliders). When using a touch screen device, I am unintentionally impacting slider components (the value is getting changed) while trying to scroll up or down the page. This does not happen with other page components, I suspect this is because sliders do (and should) respond to swipe events.
Material UI has documentation that implies a way to discern between a "scroll" and a "swipe" (https://material.io/design/interaction/gestures.html#types-of-gestures). Is there a way for me to indicate to my slider components that a "scroll" should be ignored. Or, can I discern between a vertical or horizontal swipe, telling the slider to ignore vertical swipes?
I have come up with a fairly elegant solution, I believe, which allows the user to scroll if their scroll position begins on the track but not on the thumbs. This replicates the native HTML range input so I feel that this is the best solution.
There's two parts to this
Step 1, allow touch-action on the slider root element as it is disabled by default and prevents the user from starting a scroll on the slider
const useStyles = makeStyles({
sliderRoot: {
touchAction: "auto"
}
});
return (
<Slider
classes={{
root: classes.sliderRoot
}}
...
/>
Step 2, stop propagation on the root element with a ref
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
ref.current.addEventListener(
"touchstart",
(e) => {
const isThumb = e.target?.dataset.index;
if (!isThumb) {
e.stopPropagation();
}
},
{ capture: true }
);
}
});
return (
<Slider
ref={ref}
...
/>
And here is a fully working demo on Codesandbox.
There is not going to be a completely clean solution to this other than telling your users to watch their finger placement when scrolling on mobile devices.
Using a controlled Slider, one approach would be to listen to the touchstart event and record the current pageY on the first changedTouches object. Then compare that coordinate to the pageY on the onChangeCommited event handler for the corresponding touchmove event. If the difference between the two coordinates is larger than some predefined range, then do not update the Slider value.
Inside your component using the Slider:
const delta = 50
const sliderRef = useRef(null)
const [value, setValue] = useState(0) // Or from some prop
const [touchStart, setTouchStart] = useState(0)
const debouncedHandler = useMemo(() => {
// Using lodash.debounce
return debounce((evt, value) => {
// If it is a mouse event then just update value as usual
if (evt instanceof MouseEvent) {
setValue(value)
}
}, 25)
}, [])
useLayoutEffect(() => {
if (sliderRef.current) {
sliderRef.current.addEventListener('touchstart', evt => {
setTouchStart(evt.changedTouches[0].pageY)
})
}
}, [])
return (
<Slider
value={value}
ref={sliderRef}
onChange={debouncedHandler}
onChangeCommitted={(evt, value) => {
if (evt instanceof TouchEvent) {
if (Math.abs(touchStart - evt.changedTouches[0].pageY) < delta) {
setValue(value)
}
} else {
setValue(value)
}
}}
/>
)
This will prevent the Slider from changing value on TouchEvent when the difference between the starting y-coordinate and the ending y-coordinate is larger than delta. Adjust delta to whatever value you like. The tradeoff is that you will not get as smooth of a transition when adjusting the Slider with a normal MouseEvent (or TouchEvent within the predefined range).
See the jsFiddle.
Or npm i mui-scrollable-slider-hook and use it like
import Slider from '#mui/material/Slider'
import { useMuiScrollableSlider } from 'mui-scrollable-slider-hook'
const { ref, value, onChange, onChangeCommitted } = useMuiScrollableSlider()
return <Slider ref={ref} value={value} onChange={onChange} onChangeCommitted={onChangeCommitted} />
An example of using mui-scrollable-slider-hook on codesandbox.
I've got the following React app where I'm using react-spring to animate between routes and animate different elements based on the current scroll position.
When I use overflow: scroll on the Home component I'm then unable to return anything from my handleScroll method (it just returns 0):
handleScroll(e) {
let windowScrollPosition = window.scrollY
this.setState({ windowScrollPosition: windowScrollPosition }, () => console.log(this.state.windowScrollPosition, 'this.state.windowScrollPosition'))
}
Is there a way around this?
I need to use overflow: scroll to solve this issue unfortunately.
Any help is much appreciated!
Well that would make sense. If you have set an element to scroll that is 100% height then the window would never scroll the element would.
So you need to get the scroll position from the element with elem.scrollTop
You can create a ref to the element in React
constructor(props) {
super(props);
this.scrollContainer = React.createRef();
}
return (
<div className="scroll-container" ref={this.scrollContainer}></div>
)
And then in your handle scroll method use:
handleScroll(e) {
let windowScrollPosition = this.scrollContainer.current.scrollTop | window.scrollY;
this.setState({ windowScrollPosition: windowScrollPosition }, () => console.log(this.state.windowScrollPosition, 'this.state.windowScrollPosition'))
}