How can I paginate with scroll in React? - javascript

I'm trying to paginate when the user scrolls down, I tried with this code:
import ListingPageComponent from "./ListingPageComponent";
import axios from "axios";
function ListingPageContainer() {
const listInnerRef = useRef();
const [currPage, setCurrPage] = useState(1);
const [prevPage, setPrevPage] = useState(0);
const [userList, setUserList] = useState([]);
const [lastList, setLastList] = useState(false);
try {
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(
`http://localhost:3000/quejas?limit=10&page=${currPage}`
);
console.log(response.data);
if (!response.data.docs.length) {
setLastList(true);
return;
}
setPrevPage(currPage);
setUserList([...userList, ...response.data.docs]);
};
if (!lastList && prevPage !== currPage) {
fetchData();
}
}, [currPage, lastList, prevPage, userList]);
} catch (error) {
console.log(error);
}
const onScroll = () => {
if (listInnerRef.current) {
const { scrollTop, scrollHeight, clientHeight } = listInnerRef.current;
if (scrollTop + clientHeight === scrollHeight) {
setCurrPage(currPage + 1);
}
}
};
return (
<ListingPageComponent
onScroll={onScroll}
listInnerRef={listInnerRef}
userList={userList}
/>
);
}
export default ListingPageContainer;
I can only make it work with the overflow class, but I don't want the scroll bar of the div to be visible, nor need to use the overflow class.

Related

How to make observer unchecked when page is first rendered

I am implementing infinity scroll.
When i first enter the screen observerRef will be checked
The page State goes straight to 1 Send two axios requests
I tried many ways, but couldn't solve it, please help
here is my code
function SearchCookRoom() {
const [page, setPage] = useState(0);
const observerRef = useRef(true);
const preventObserverRef = useRef(true);
const endRef = useRef(false);
const observerHandler = entries => {
// console.log(entries);
const target = entries[0];
if (
!endRef.current &&
target.isIntersecting &&
preventObserverRef.current
) {
preventObserverRef.current = false;
setPage(prev => prev + 1);
}
};
useEffect(() => {
const observer = new IntersectionObserver(observerHandler, {
threshold: 0.5,
});
if (observerRef.current) {
observer.observe(observerRef.current);
}
return () => {
observer.disconnect();
};
}, []);
const onSaveEnteredItem = item => {
setEnterdItme(item);
};
const getData = useCallback(async () => {
try {
const allCookRoom = await axios({
url: `${
!enterdItme
? `${LIST_URL}?page=${page}&size=15`
: `${SEARCH_URL}/${enterdItme}?page=${page}&size=15`
}`,
});
if (page === allCookRoom.data.totalPages) {
endRef.current = true;
}
if (enterdItme) {
setCookRoom([]);
setPage(0);
}
setCookRoom(prev => {
return [...new Set([...prev, ...allCookRoom.data.content])];
});
preventObserverRef.current = true;
} catch (error) {
console.log(error);
}
}, [page, enterdItme]);
useEffect(() => {
getData();
}, [enterdItme, page]);
console.log(cookRoom);
return (
<>
// Some code
<li ref={observerRef}/>
</>
);
}
observerRef checks if the scroll is at the bottom of the page
The page State starts at 0 As soon as it is rendered it changes to 1 and sends an axios request

React hooks render more hooks issue

I have used more than two react hooks together and I get this issue where there is no error with my frontend other than render hooks. I don't know how to resolve this error. I tried even using useState method.
If there is a possible fix can you let me know?
const Login = () => {
const { register, handleSubmit, setValue } = useForm();
const onSubmit = useCallback((formData) => {
const { email, password } = formData;
console.log(formData);
}, []);
const windowWidth = Dimensions.get("window").width;
const windowHeight = Dimensions.get("window").height;
const [loaded] = useFonts({
Lato: require("../assets/fonts/Lato-Regular.ttf"),
});
if (!loaded) {
return null;
}
const onChangeField = useCallback(
(name) => (text) => {
setValue(name, text);
},
[]
);
useEffect(() => {
register("email");
register("password");
}, [register]);
return (
);
};
export default Login;
Your app must call the same number of hooks every render. So you need to move
if (!loaded) {
return null;
}
to be below all of your hooks:
const Login = () => {
const { register, handleSubmit, setValue } = useForm();
const onSubmit = useCallback((formData) => {
const { email, password } = formData;
console.log(formData);
}, []);
const windowWidth = Dimensions.get("window").width;
const windowHeight = Dimensions.get("window").height;
const [loaded] = useFonts({
Lato: require("../assets/fonts/Lato-Regular.ttf"),
});
const onChangeField = useCallback(
(name) => (text) => {
setValue(name, text);
},
[]
);
useEffect(() => {
register("email");
register("password");
}, [register]);
if (!loaded) {
return null;
}
return (
);
};
export default Login;

Why isn't the data rendered in sorted order

Why isn't the rendered data in sorted order
I am passing rides as a prop in the NearestRides component and inside the NearestRides component, first i am sorting the rides and setting to sortedRides and then i am mapping sortedRides.
but why is the sortedRides not sorted?
is sortedRides getting rendered before getting sorted? if so, how do i sort rides before rendering?
Rides.js
import { useEffect, useState } from "react";
import Navbar from "./Navbar";
import NearestRides from "./NearestRides";
const Rides = () => {
const [rides, setRides] = useState([]);
const [user, setUser] = useState({});
useEffect(() => {
const fetchRides = async () => {
const data = await fetch('https://assessment.api.vweb.app/rides');
const json = await data.json();
setRides(json);
}
const fetchUser = async () => {
const data = await fetch('https://assessment.api.vweb.app/user');
const json = await data.json();
console.log(json);
setUser(json);
}
const makeNetworkCalls = async() => {
await fetchRides();
await fetchUser();
}
makeNetworkCalls().catch((e) => {
console.log(e);
})
}, [])
useEffect(() => {
const calculateDistance = async(path, user_station) => {
let min = Math.abs(user_station - path[0]);
for(let i = 0; i<path.length; i++){
if(path[i] === user_station){
return 0;
}
if(Math.abs(path[i] - user_station) < min){
min = Math.abs(path[i] - user_station);
}
}
return min;
}
const updaterides = async () => {
rides.map(async (ride) => {
ride.distance = await calculateDistance(
ride.station_path,
user.station_code
);
});
};
if (rides?.length > 0) {
updaterides().catch((e) => {
console.log(e);
});
}
}, [rides,user]);
return (
<div>
<Navbar user = {user}/>
<div className="home">
<NearestRides rides = {rides}/>
</div>
</div>
);
}
export default Rides;
NearestRides.js
import { useEffect, useState } from "react";
const NearestRides = ({rides}) => {
const [sortedRides, setSortedRides] = useState([]);
useEffect(() => {
const sortRides = async() => {
const sorted = await rides.sort((ride1,ride2) => {
return ride1.distance > ride2.distance ? 1 : -1;
})
setSortedRides(sorted);
}
sortRides().catch((e) => console.log(e));
}, [rides]);
return(
<div className="rides">
{console.log(sortedRides)}
{sortedRides?.map((ride) => {
return (
<div className="ride-detail">
<img src={ride.map_url} alt="Ride_map" />
<div>
<p>Ride Id : {ride.id}</p>
<p>Origin Station : {ride.origin_station_code}</p>
<p>Station Path : {ride.station_path}</p>
<p>Date : {ride.date}</p>
<p>Distance : {ride.distance}</p>
</div>
</div>
)
})}
</div>
)
}
export default NearestRides;
It looks like that when you are calculating the distance and adding the distance property to rides array, you are not actually setting it again to the rides using setRides.
And so, the distance is never part of the rides array when received in child nearestRides and hence sorting method is not working in nestedRides.
But setting the rides in the useEffect with deps will create infinite loop.
So suggest to calculate the distance in the useEffect with no deps - ie right after you fetch the rides.

Filter array and show results in React

I am trying to filter my results via button onClicks in React, but for some reason its not working?
https://stackblitz.com/edit/react-x7tlxr?file=src/App.js
import React, {useEffect, useState} from "react";
import axios from 'axios';
import "./style.css";
export default function App() {
const [fetchData, setFetchData] = useState([]);
const [loading, setLoading] = useState(true);
const [isError, setIsError] = useState(false);
const url = 'https://jsonplaceholder.typicode.com/todos';
useEffect(() => {
let mounted = true;
const loadData = async () => {
try {
const response = await axios(url);
if (mounted) {
setFetchData(response.data);
setLoading(false);
setIsError(false);
console.log('data mounted')
}
} catch (err) {
setIsError(true)
setLoading(false);
setFetchData([]);
console.log(err);
}
};
loadData();
return () => {
mounted = false;
console.log('cleaned');
};
},
[url]
);
const renderdata = () => {
if (fetchData) {
return (
<div>{fetchData.map(inner => {
return (
<React.Fragment key={inner.id}>
<p>{inner.title}</p>
</React.Fragment>
)
})}</div>
)
} else {
<p>No data to display!</p>
}
}
const handle1 = () => {
const result1 = fetchData.filter((inner) => inner.id === '1');
setFetchData(result1);
}
const handle2 = () => {
const result2 = fetchData.filter((inner) => inner.id === '2');
setFetchData(result2);
}
const handleAll = () => {
setFetchData(fetchData);
}
return (
<div>
<button onClick={handleAll}>Show all</button>
<button onClick={handle1}>Filter by id 1</button>
<button onClick={handle2}>Filter by id 2</button>
{renderdata()}
</div>
);
}
The id is a number, not a string, so you filter(s) need to be changed like that:
const result1 = fetchData.filter((inner) => inner.id === 1);
You have another problem, is that you change the whole data set when you filter, so once you've filtered, you can't "unfilter" or filter again on another id.
You need to maintain the original fetchedData unchanged.
This example shows how it can be fixed.
I found 2 issues in your code
Id in the API result is numeric not string
inner.id === '2'. so this will return false inner.id === 2 you need to use like this
when you assign the filtered value to the original array it will of course change the original array so when you try to filter it second time you don't have the original API result in the fetch data array because it is already filtered
So i have created one more array filteredData
This will work.
import React, {useEffect, useState} from "react";
import axios from 'axios';
import "./style.css";
export default function App() {
const [fetchData, setFetchData] = useState([]);
const [filteredData, setFileredData] = useState([]);
const [loading, setLoading] = useState(true);
const [isError, setIsError] = useState(false);
const url = 'https://jsonplaceholder.typicode.com/todos';
useEffect(() => {
let mounted = true;
const loadData = async () => {
try {
const response = await axios(url);
if (mounted) {
setFetchData(response.data);
setFileredData(response.data)
setLoading(false);
setIsError(false);
console.log('data mounted')
}
} catch (err) {
setIsError(true)
setLoading(false);
setFetchData([]);
console.log(err);
}
};
loadData();
return () => {
mounted = false;
console.log('cleaned');
};
},
[url]
);
const renderdata = () => {
if (filteredData) {
return (
<div>{filteredData.map(inner => {
return (
<React.Fragment key={inner.id}>
<p>{inner.title}</p>
</React.Fragment>
)
})}</div>
)
} else {
<p>No data to display!</p>
}
}
const handle1 = () => {
const result1 = fetchData.filter((inner) => inner.id === 1);
setFileredData(result1);
}
const handle2 = () => {
const result2 = fetchData.filter((inner) => inner.id === 2);
setFileredData(result2);
}
const handleAll = () => {
setFileredData(fetchData);
}
return (
<div>
<button onClick={handleAll}>Show all</button>
<button onClick={handle1}>Filter by id 1</button>
<button onClick={handle2}>Filter by id 2</button>
{renderdata()}
</div>
);
}

Problem with useEffect and navbar active on scroll "To fix, cancel all subscriptions"

I'm trying to make active anchors in navbar navigation on scroll. Everything is working until I don't change page and return back to home page, then when I scroll page I get an error from useEffect hook " Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function. " How I should cancel all subscriptions ?
useEffect code :
const [headerText, setHeader] = useState(false);
let mount = false;
useEffect(() => {
if (!mount) {
scrollActiveNav();
scrollStickyNav((header) => {
setHeader(header);
});
}
return () => {
mount = true;
};
}, []);
Sticky navbar function :
const scrollStickyNav = (cb) => {
const scrollSticky = window.addEventListener("scroll", () => {
const header = document.getElementById("navbar");
if (window.pageYOffset >= 80) {
header.classList.add("navbar-sticky");
header.classList.remove("absolute");
cb(true);
} else {
header.classList.remove("navbar-sticky");
header.classList.add("absolute");
cb(false);
}
});
return window.removeEventListener("scroll", scrollSticky);
}
Acitve link anchor in navabar function:
const scrollActiveNav = () => {
const activeNav = window.addEventListener('DOMContentLoaded', () => {
const options = {
threshold: 0.5
};
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const id = entry.target.id;
if (entry.isIntersecting && entry.intersectionRatio > 0.5) {
document.querySelector(`.navbar-nav li a[href="${id}"]`).classList.add('active');
} else {
document.querySelector(`.navbar-nav li a[href="${id}"]`).classList.remove('active');
}
});
}, options);
document.querySelectorAll('section[id]').forEach((section) => {
observer.observe(section);
});
});
return window.removeEventListener("DOMContentLoaded", activeNav);
}
Try change this line let mount = false; for this const mount = useRef(false).
const [headerText, setHeader] = useState(false);
let mount = useRef(false);
useEffect(() => {
if (!mount.current) {
scrollActiveNav();
scrollStickyNav((header) => {
setHeader(header);
});
mount.current = true;
}
}, []);
Did you try to do something like this?
useEffect(() => {
scrollActiveNav();
const activeNav = window.addEventListener('DOMContentLoaded', () => {
const options = {
threshold: 0.5
};
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const id = entry.target.id;
if (entry.isIntersecting && entry.intersectionRatio > 0.5) {
document.querySelector(`.navbar-nav li a[href="${id}"]`).classList.add('active');
} else {
document.querySelector(`.navbar-nav li a[href="${id}"]`).classList.remove('active');
}
});
}, options);
document.querySelectorAll('section[id]').forEach((section) => {
observer.observe(section);
});
});
return () => {
window.removeEventListener("DOMContentLoaded", activeNav);
};
}, []);

Categories

Resources