React Infinite Scroll - Intersection Observer API always jumping back to top - javascript

I´m developing a React application with infinite scrolling using the Intersection Observer API (not using any 3rd library for infinite scrolling). My backend handles pagination properly, and the infinite scrolling also fetches the next parts when reaching the threshold. The problem is, that each time I scroll down and reach the threshold, the window is scrolled back to the top, which is not what I expect. It seems that not just the new items are going to be rendered, but instead the entire list of items.
Here is the way I´ve implemented it in React with Redux so far:
import React, {useEffect, useRef, useCallback} from "react";
import Spinner from "../../UI/Spinner";
import {withRouter} from "react-router-dom";
import {connect} from "react-redux";
import * as actions from "../../store/actions/index";
const List = props => {
const {items, next, loading, loadMoreData} = props;
const loader = useRef(null);
const loadMore = useCallback((entries) => {
const target = entries[0];
if (target.isIntersecting && next) {
!loading && loadMoreData(next);
}
}, [loading, next, loadMoreData]);
useEffect(() => {
const options = {
threshold: 1.0
};
// Observer API
const observer = new IntersectionObserver(loadMore, options);
const currentLoader = loader.current;
if (loader && currentLoader) {
observer.observe(currentLoader);
}
return () => observer.unobserve(loader.current);
}, [loader, loadMore]);
const content = <Spinner/>;
if (!loading) {
content = items.map((item, index) => return (<div key={index}>{item}</div>))
}
return (
<div>
{content}
<div ref={loader}></div>
</div>
)
}
const mapStateToProps = state => {
return {
items: state.items.items,
next: state.items.next,
loading: state.items.loading,
error: state.items.error
};
};
const mapDispatchToProps = dispatch => {
return {
fetchMoreData: (next) => dispatch(actions.fetchDataStart(next)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(List));
So summing up the problem is the following:
After scrolling down the screen and after reaching the set threshold the data is fetched successfully and added to my redux store. But the entire list is rendered again on the top of the page and I have to scroll down again. Any ideas how to solve this?

This topic can be closed. It was a small and stupid mistake. The re-rendering of the whole list is caused by the if statement where I populate my content variable. Removing this solves the issue and infinite scroll works like a charm.

Related

Is the any way to identify whether a query is in view or not in react query

Assume I have 3 queries x, y and z. I want to update the query x only when it is in view. Is there any way to identify it ?
I tried to maintain the view query key as a global state, but it seems not working fine. Is there any way to identify without maintaining viewing query key as global state.
Is there any possible way to get list of viewing queries ???
First of all, you need to hold the state of the component if it's in the viewport or not. The IntersectionObserver API allows you to detect when an element enters or leaves the viewport, which you can use to update the state of your component.
Then you can use that state as a key in your useQuery. Also you can use it in enabled option if you want to prevent refetch when item is not on the viewport.
import React, { useState, useEffect, useRef } from 'react';
import { useQuery } from 'react-query';
function Test() {
const [isInViewport, setIsInViewport] = useState(false);
const ref = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsInViewport(true);
} else {
setIsInViewport(false);
}
});
});
observer.observe(ref.current);
return () => {
observer.unobserve(ref.current);
};
}, []);
useQuery(
[isInViewport],
() => (
/// your query
),
{
enabled: isInViewport,
},
);
return (
<div ref={ref}>
<div>{isInViewport ? 'In viewport' : 'Not in viewport'}</div>
</div>
);
}
export default Test;

React-router-dom (v6) go back only within application

Is there a built-in way in react-router-dom v6, to go back to the previous page, BUT in case the previous page is out of the context of the application, to route to the root and to thus not out of the application.
Example: I surf to a www.thing.com/thingy from www.google.com, this page (www.thing.com/thingy) has a go back button on it => when I click on the go back button => I am redirected to www.google.com instead of the wanted behaviour a redirect to www.thing.com.
Mockup of an example page.
I have tried several implementations and searched through the documentation but couldn't find a built-in way to resolve this. As far as I can see there isn't a way. I can however make something custom to resolve my issue if its not.
import { useNavigate } from 'react-router-dom';
function YourApp() {
const navigate = useNavigate();
return (
<>
<button onClick={() => navigate(-1)}>go back</button>
</>
);
}
I solved it by keeping track of the history.
If a user had not yet been on the page, I redirect them to the homepage.
Else redirect them to the previous page.
import {
useEffect
} from 'react';
import {
createContext,
useMemo,
useState
} from 'react';
import {
useLocation
} from 'react-router-dom';
export const LocationHistoryContext = createContext({});
const LocationHistoryProvider = ({
children
}) => {
const [locationHistory, setLocationHistory] = useState(
new Set(),
);
const location = useLocation();
useEffect(() => {
// if pathname has changed, add it to the history
let path = location.pathname.match(/^\/([^/])*/)[0];
setLocationHistory((prev) => new Set([path, ...prev]));
}, [location.pathname, location]);
const context = useMemo(() => {
return {
/* if the user has visited more than one page */
hasHistory: locationHistory.size > 1,
};
}, [locationHistory]);
return ( <
LocationHistoryContext.Provider value = {context}>
{
children
}
</LocationHistoryContext.Provider>
);
};
export default LocationHistoryProvider;

React useRef scrollIntoView only fires once

I'm trying to scroll to an element when it comes into view. The problem is that it only works on a reload when it's already in view.
I've tried debugging it, but the ref seems to be correct and the conditionals pass. There is no return or error message so I don't know how to debug this further.
The hook works as it should so I'm really struggling to figure out what the cause is...
I need to put this in useEffect later on, but even this basic setup doesn't work. Any help is very much appreciated!
EDIT: I need to get this in the center of the screen so that I can overtake the scroll and animate the element on scroll. If I already start that functionality without it being centered, it'll stick to the bottom of the screen while it animates.
This is the component
const Component = () => {
const sectionRef = useRef<HTMLDivElement>(null);
const isOnScreen = useOnScreen(sectionRef);
if (isOnScreen && sectionRef?.current) {
sectionRef.current.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest'});
}
return (
<section ref={sectionRef}>
// ...child components
</section>
)
}
export default Component
This is the hook
import { useEffect, useState, useRef, RefObject } from 'react';
export default function useOnScreen(ref: RefObject<HTMLElement>) {
const observerRef = useRef<IntersectionObserver | null>(null);
const [isOnScreen, setIsOnScreen] = useState(false);
useEffect(() => {
observerRef.current = new IntersectionObserver(([entry]) =>
setIsOnScreen(entry.isIntersecting)
);
}, []);
useEffect(() => {
if (ref.current) {
observerRef.current?.observe(ref.current);
}
return () => {
observerRef.current?.disconnect();
};
}, [ref]);
return isOnScreen;
}

ReactJS how to render a component only when scroll down and reach it on the page?

I have a react component Data which includes several charts components; BarChart LineChart ...etc.
When Data component starts rendering, it takes a while till receiving the data required for each chart from APIs, then it starts to respond and render all the charts components.
What I need is, to start rendering each chart only when I scroll down and reach it on the page.
Is there any way could help me achieving this??
You have at least three options how to do that:
Track if component is in viewport (visible to user). And then render it. You can use this HOC https://github.com/roderickhsiao/react-in-viewport
Track ‘y’ scroll position explicitly with https://react-fns.netlify.com/docs/en/api.html#scroll
Write your own HOC using Intersection Observer API https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
To render component you may need another HOC, which will return Chart component or ‘null’ based on props it receives.
I have tried many libraries but couldn't find something that best suited my needs so i wrote a custom hook for that, I hope it helps
import { useState, useEffect } from "react";
const OPTIONS = {
root: null,
rootMargin: "0px 0px 0px 0px",
threshold: 0,
};
const useIsVisible = (elementRef) => {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
if (elementRef.current) {
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.unobserve(elementRef.current);
}
});
}, OPTIONS);
observer.observe(elementRef.current);
}
}, [elementRef]);
return isVisible;
};
export default useIsVisible;
and then you can use the hook as follows :
import React, { useRef } from "react";
import useVisible from "../../hooks/useIsVisible";
function Deals() {
const elemRef = useRef();
const isVisible = useVisible(elemRef);
return (
<div ref={elemRef}>hello {isVisible && console.log("visible")}</div>
)}
I think the easiest way to do this in React is using react-intersection-observer.
Example:
import { useInView } from 'react-intersection-observer';
const Component = () => {
const { ref, inView, entry } = useInView({
/* Optional options */
threshold: 0,
});
useEffect(()=>{
//do something here when inView is true
}, [inView])
return (
<div ref={ref}>
<h2>{`Header inside viewport ${inView}.`}</h2>
</div>
);
};
I also reccommend using triggerOnce: true in the options object so the effect only happens the first time the user scrolls to it.
you can check window scroll position and if the scroll position is near your div - show it.
To do that you can use simple react render conditions.
import React, {Component} from 'react';
import PropTypes from 'prop-types';
class MyComponent extends Component {
constructor(props){
super(props);
this.state = {
elementToScroll1: false,
elementToScroll2: false,
}
this.firstElement = React.createRef();
this.secondElement = React.createRef();
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll(e){
//check if scroll position is near to your elements and set state {elementToScroll1: true}
//check if scroll position is under to your elements and set state {elementToScroll1: false}
}
render() {
return (
<div>
<div ref={this.firstElement} className={`elementToScroll1`}>
{this.state.elementToScroll1 && <div>First element</div>}
</div>
<div ref={this.secondElement} className={`elementToScroll2`}>
{this.state.elementToScroll2 && <div>Second element</div>}
</div>
</div>
);
}
}
MyComponent.propTypes = {};
export default MyComponent;
this may help you, it's just a quick solution. It will generate you some rerender actions, so be aware.

Redirect to the previous page using session history/management using nodejs/react/javascript

import React, { PropTypes } from 'react';
import createMemoryHistory from 'history/lib/createMemoryHistory';
import { ArrowButton } from '../../components';
const history = createMemoryHistory();
const unlisten = history.listen((location, action) => {
console.log(action, location.pathname, location.state);
});
history.push('/home', { some: 'state' });
createMemoryHistory({
initialEntries: ['/'], // The initial URLs in the history stack
initialIndex: 0, // The starting index in the history stack
keyLength: 6, // The length of location.key
// A function to use to confirm navigation with the user. Required
// if you return string prompts from transition hooks (see below)
getUserConfirmation: null,
});
history.listen((location, action) => {
console.log(`The current URL is ${location.pathname}${location.search}${location.hash}`);
console.log(`The last navigation action was ${action}`);
});
const genericMessage = 'We have a problem.';
const RegistrationError = ({
reason,
returnToUrl,
goBack,
}) => (
<div className="registration-error">
<h2 className="register-title">Sorry!</h2>
<p>{ reason || genericMessage }</p>
{ !reason && (
<ArrowButton
onClick={() => returnToUrl(history.go(-1))}
>
Close
</ArrowButton>
I am new to Nodejs, and having difficulties understanding some concept. The problem that I have is when I click on the Close button, the page should redirect to the previously viewed site. However this is not working. What am i doing wrong? If anyone could point me to the right direction. I have checked similar problems on stack overflow, which did not help me.

Categories