Sync scroll react. div block with main scroll on window - reactjs

I want to synchronize a divs scroll with a body scroll.
I tried some examples with two divs but I couldn't manage fix it with the body scroll.
Sample code with two divs: https://codesandbox.io/s/react-custom-scroll-sync-of-2-divs-10xpi
My Code
https://codesandbox.io/s/funny-rain-ditbv
import "./styles.css";
import { useRef } from "react";
export default function App() {
const firstDivRef = useRef();
const secondDivRef = useRef();
const handleScrollFirst = (scroll) => {
secondDivRef.current.scrollTop = scroll.target.scrollTop;
};
const handleScrollSecond = (scroll) => {
firstDivRef.current.scrollTop = scroll.target.scrollTop;
};
return (
<div
className="App"
style={{
display: "flex",
}}
>
<div
onScroll={handleScrollFirst}
ref={firstDivRef}
style={{
height: "500px",
overflow: "scroll",
backgroundColor: "#FFDAB9",
position: "sticky",
top: "0px"
}}
>
<div style={{ height: 5000, width: 300 }}>
The first div (or it can be tbody of a table and etc.)
{[...new Array(1000)].map((_, index) => {
const isEven = index % 2 === 0;
return (
<div style={{ backgroundColor: isEven ? "#FFFFE0 " : "#FFDAB9" }}>
{index}
</div>
);
})}
</div>
</div>
<div
onScroll={handleScrollSecond}
ref={secondDivRef}
style={{
height: "100%",
backgroundColor: "#EEE8AA"
}}
>
<div style={{ height: 5000, width: 200 }}>
The second div
{[...new Array(1000)].map((_, index) => {
const isEven = index % 2 === 0;
return (
<div style={{ backgroundColor: isEven ? "#FFFFE0 " : "#FFDAB9" }}>
{index}
</div>
);
})}
</div>
</div>
</div>
);
}

It was easy to use different divs rather than using a div and window.
But finally managed to run it with a div and the body.
The trick is they block each other since they listen each others values.
import "./styles.css";
import { useEffect, useRef, useState } from "react";
export default function App() {
const firstDivRef = useRef();
const [scrollTop, setScrollTop] = useState(0);
const [disableBodyScroll, setDisableBodyScroll] = useState(false);
const handleScrollFirst = (scroll) => {
setScrollTop(scroll.target.scrollTop);
};
useEffect(() => {
if (firstDivRef.current && !disableBodyScroll) {
firstDivRef.current.scrollTop = scrollTop;
}
if (disableBodyScroll) {
window.scrollTo(0, scrollTop);
}
}, [firstDivRef, scrollTop, disableBodyScroll]);
useEffect(() => {
const onScroll = () => {
console.log(disableBodyScroll, window.scrollY);
if (!disableBodyScroll) {
setScrollTop(window.scrollY);
}
};
// clean up code
window.removeEventListener("scroll", onScroll);
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, [disableBodyScroll]);
return (
<div
className="App"
style={{
display: "flex"
}}
>
<div
onMouseEnter={() => setDisableBodyScroll(true)}
onMouseLeave={() => setDisableBodyScroll(false)}
onScroll={handleScrollFirst}
ref={firstDivRef}
style={{
height: "500px",
overflow: "scroll",
backgroundColor: "#FFDAB9",
position: "sticky",
top: "0px"
}}
>
<div style={{ height: 5000, width: 300 }}>
The first div (or it can be tbody of a table and etc.)
{[...new Array(1000)].map((_, index) => {
const isEven = index % 2 === 0;
return (
<div style={{ backgroundColor: isEven ? "#FFFFE0 " : "#FFDAB9" }}>
{index}
</div>
);
})}
</div>
</div>
<div
style={{
height: "100%",
backgroundColor: "#EEE8AA"
}}
>
<div style={{ height: 5000, width: 200 }}>
The second div
{[...new Array(1000)].map((_, index) => {
const isEven = index % 2 === 0;
return (
<div style={{ backgroundColor: isEven ? "#FFFFE0 " : "#FFDAB9" }}>
{index}
</div>
);
})}
</div>
</div>
</div>
);
}
https://codesandbox.io/s/ancient-dream-tzuel?file=/src/App.js

Try the next example. This is a quick sketch but maybe it will help you.
https://codesandbox.io/s/gallant-goldwasser-19g4d?file=/src/App.js

Related

The react-use-cart needs the string ( id ) but my api data only generates the object ( _id ), how can I change it and use it normally?

I'm creating a cart and I need to add the item, the function has the id of the item as a requirement, but my item only generates the _id and not just the id. The problem to be simple for those who have a lot of knowledge, thanks in advance to the collaborators
https://www.npmjs.com/package/react-use-cart
Index.js ( Product details )
import React, { useState, useEffect } from "react";
import { useParams } from "react-router";
import Popup from "reactjs-popup";
import "reactjs-popup/dist/index.css";
import { ThreeDots } from "react-loader-spinner";
import { useCart } from "react-use-cart";
//Local
import Nav from "../../components/Nav";
import api from "../../../services/api";
import Best from "../Best/index";
import { TitleCaurosel } from "../style";
//Icons / Images
import { AiFillStar } from "react-icons/ai";
import { BsArrowLeft, BsArrowRight } from "react-icons/bs";
//Style
import {
Product,
Images,
Main,
Other,
Details,
Title,
Description,
Stars,
Colors,
Color,
ColorSelected,
Price,
Cart,
} from "./style";
const Index = () => {
const params = useParams();
const { addItem } = useCart();
const [data, setData] = useState(undefined);
const [error, setError] = useState(undefined);
const [color, setColor] = useState(undefined);
const [nameColor, setNameColor] = useState(undefined);
const [index, setIndex] = useState(0);
const [popup, setPopup] = useState(false);
const [current, setCurrent] = useState(0);
const [main, setMain] = useState(undefined);
const ref = React.createRef();
const { height, width } = useWindowDimensions();
function getWindowDimensions() {
const { innerWidth: width, innerHeight: height } = window;
return {
width,
height,
};
}
function useWindowDimensions() {
const [windowDimensions, setWindowDimensions] = useState(
getWindowDimensions()
);
useEffect(() => {
function handleResize() {
setWindowDimensions(getWindowDimensions());
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return windowDimensions;
}
const config = {
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
};
useEffect(() => {
api.get(`/items/${params.id}`).then((response) => setData(response.data));
}, []);
const addToCart = () => {
if (color !== undefined) {
api
.post(
`/cart`,
{
itemId: params.id,
quantity: 1,
colors: data.images,
colorSelected: color,
img: data.images[0].src,
},
config
)
.then((response) => {
if (response.status === 200 || response.status === 201) {
window.location.pathname = "/Cart";
}
});
} else {
setError(1);
}
};
const numberFormat = (value) =>
new Intl.NumberFormat("pt-br", {
style: "currency",
currency: "BRL",
}).format(value);
const handleTab = (index) => {
setMain(undefined);
setIndex(index);
if (color !== undefined && nameColor !== undefined) {
setColor(undefined);
setNameColor(undefined);
}
};
const nextSlide = () => {
setCurrent(current === data.images.length - 1 ? 0 : current + 1);
};
const prevSlide = () => {
setCurrent(current === 0 ? data.images.length - 1 : current - 1);
};
useEffect(() => {
if (data !== undefined) {
if (!Array.isArray(data.images) || data.images.length <= 0) {
return null;
}
}
});
const changeColor = (id, name) => {
setColor(id);
setNameColor(name);
setError(undefined);
const filter = data.images.filter((item) => item._id === id);
setMain(filter[0].src);
};
return data !== undefined ? (
<div>
{/*Popup*/}
{popup === true && (
<Popup
open={true}
position="center center"
onClose={() => setPopup(false)}
>
<section className="slider">
<BsArrowLeft className="left-arrow" onClick={prevSlide} />
<BsArrowRight className="right-arrow" onClick={nextSlide} />
{data.images.map((slide, index) => {
return (
<div
className={index === current ? "slide active" : "slide"}
key={index}
>
{index === current && (
<div
style={{
width: width / 2,
height: height / 2,
backgroundImage: `url(${slide.src})`,
backgroundRepeat: "no-repeat",
backgroundSize: "contain",
backgroundPosition: "center",
}}
/>
)}
</div>
);
})}
</section>
</Popup>
)}
<Nav />
<div
style={{
height: "90vh",
display: "grid",
justifyContent: "center",
alignItems: "center",
gridTemplateColumns: "85%",
gridTemplateRows: "100%",
gridColumnGap: "5px",
}}
>
<Product key={data._id}>
<Images>
<div
style={{
display: "grid",
marginRight: "3%",
height: height / 1.3,
}}
ref={ref}
>
<Other
style={{
width: width / 10,
backgroundImage: `url(${data.images[0].src})`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "60%",
}}
alt={`Image de ${data.name}`}
key={0}
onClick={() => handleTab(0)}
/>
{data.images[1] !== undefined && (
<Other
style={{
marginTop: "5%",
width: width / 10,
backgroundImage: `url(${data.images[1].src})`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "60%",
}}
alt={`Image de ${data.images[1].name}`}
key={1}
onClick={() => handleTab(1)}
/>
)}
{data.images[2] !== undefined && (
<Other
style={{
marginTop: "5%",
width: width / 10,
backgroundImage: `url(${data.images[2].src})`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "60%",
}}
alt={`Image de ${data.name}`}
key={2}
onClick={() => handleTab(2)}
/>
)}
{data.images[3] !== undefined && (
<Other
style={{
marginTop: "5%",
width: width / 10,
backgroundImage: `url(${data.images[3].src})`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "60%",
}}
alt={`Image de ${data.name}`}
key={1}
onClick={() => handleTab(3)}
/>
)}
{data.images[4] !== undefined && (
<div>
{data.images.length < 5 ? (
<Other
style={{
width: width / 10,
backgroundImage: `url(${data.images[3].src})`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "60%",
}}
alt={`Image de ${data.name}`}
key={2}
onClick={() => handleTab(3)}
/>
) : (
<Other
style={{
width: width / 10,
}}
onClick={() => setPopup(true)}
alt={`Image de ${data.name}`}
>
<p>+ 5</p>
</Other>
)}
</div>
)}
</div>
<div
style={{
backgroundColor: "#F6F6F6",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Main
style={{
backgroundImage: `url(${
main === undefined ? data.images[index].src : main
})`,
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
backgroundSize: "80%",
width: width / 2.8,
height: height / 1.5,
}}
/>
</div>
</Images>
<Details>
<Title>{data.name}</Title>
<Description>{data.description}</Description>
<Stars>
<AiFillStar />
<AiFillStar />
<AiFillStar />
<AiFillStar />
<AiFillStar />
<p>441 Avaliações</p>
</Stars>
<Colors>
{error === undefined && color === undefined && (
<p style={{ color: "#000" }}>Selecione uma cor:</p>
)}
{error === 1 && color === undefined && (
<p style={{ color: "#ff0000" }}>
Selecione uma cor: (Obrigatório)
</p>
)}
{error === undefined &&
color !== undefined &&
nameColor !== undefined && (
<p style={{ color: "#000" }}>Cor selecionada: {nameColor}</p>
)}
<div
style={{
display: "flex",
marginTop: "1%",
width: "100%",
flexWrap: "wrap",
alignItems: "center",
}}
>
{data.images.map((item) =>
color === item._id ? (
<ColorSelected onClick={() => setColor(undefined)}>
<div style={{ backgroundColor: item.color }} />
</ColorSelected>
) : (
<Color
style={{ backgroundColor: item.color }}
onClick={() => {
changeColor(item._id, item.name);
}}
/>
)
)}
</div>
</Colors>
<Price>{numberFormat(data.price)}</Price>
{console.log(data)}
<Cart onClick={() => addItem(data)}>
Adicionar ao carrinho
</Cart>
</Details>
</Product>
</div>
<div style={{ paddingLeft: "130px" }}>
<TitleCaurosel>Você pode gostar!</TitleCaurosel>
<Best />
</div>
</div>
) : (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: width,
height: height,
}}
>
<ThreeDots
height="300"
width="300"
radius="9"
color="#8c6d55"
ariaLabel="three-dots-loading"
wrapperStyle={{}}
wrapperClassName=""
visible={true}
/>
</div>
);
};
export default Index;
Cart.js
import React, { useState, useEffect } from "react";
import Nav from "../components/Nav";
import Select from "react-select";
import api from "../../services/api";
import { ThreeDots } from "react-loader-spinner";
import { useCart } from "react-use-cart";
//Icons
import { BiTrash } from "react-icons/bi";
import { MdOutlineEditLocationAlt } from "react-icons/md";
import { HiOutlineShoppingBag } from "react-icons/hi";
//Style
import {
Container,
Box1,
Box2,
Box3,
Title,
Product,
Image,
Name,
Price,
Border,
Stock,
PriceTotal,
Delete,
Cupom,
BorderTraced,
Subtotal,
Items,
Total,
Checkout,
NextToBuy,
Empty,
} from "./style";
const Index = () => {
function getWindowDimensions() {
const { innerWidth: width, innerHeight: height } = window;
return {
width,
height,
};
}
function useWindowDimensions() {
const [windowDimensions, setWindowDimensions] = useState(
getWindowDimensions()
);
useEffect(() => {
function handleResize() {
setWindowDimensions(getWindowDimensions());
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return windowDimensions;
}
const { height, width } = useWindowDimensions();
const {
isEmpty,
items,
cartTotal,
updateItemQuantity,
removeItem,
emptyCart,
} = useCart();
return (
<Container>
{isEmpty ? (
<Empty>
<Nav />
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
textAlign: "center",
height: "80vh",
}}
>
<div>
<HiOutlineShoppingBag className="icon-empty" />
<h1>Sua sacola está vazia</h1>
<p className="text-empty">
Parece que você não adicionou nada a sua sacola. <br /> Vá em
frente e explore as principais categorias.
</p>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
textAlign: "center",
}}
>
<a className="button" href="/#Produtos">
Explorar produtos
</a>
</div>
</div>
</div>
</Empty>
) : (
<Container>
<Nav />
<div
style={{
display: "flex",
justifyContent: "center",
height: height,
marginTop: "3%",
marginBottom: "10%",
}}
>
<Box1 style={{ width: width / 2.8, height: height }}>
<Title>Seus items</Title>
</Box1>
<div>
<Box2 style={{ width: width / 4, height: height / 5 }}>
<Title>Frete</Title>
<p className="title">Endereço:</p>
<span></span>
<span></span>
<p className="title">Estimativa de entrega:</p>
<span>30/12/2022</span>
<MdOutlineEditLocationAlt />
</Box2>
<Box3 style={{ width: width / 4, height: height / 1.4 }}>
<Cupom>
<input placeholder="Cupom de desconto" />
<button>Aplicar</button>
</Cupom>
<BorderTraced />
<Subtotal>
<p>Subtotal</p>
<p></p>
</Subtotal>
<Items>
<div>
<p>Frete</p>
<p>R$ 399,99</p>
</div>
</Items>
<BorderTraced />
<Total>
<p>Total</p>
<p></p>
</Total>
<Checkout>Finalizar compra</Checkout>
<NextToBuy
onClick={() => {
window.location.pathname = "/Products";
}}
>
Continuar comprando
</NextToBuy>
</Box3>
</div>
</div>
</Container>
)}
</Container>
);
};
export default Index;
Index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import Routes from './routes';
import { CartProvider } from 'react-use-cart';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<CartProvider>
<Routes />
</CartProvider>
</React.StrictMode>
);
Not sure if this is the best/most efficient approach, but you could always create an Item object with the attributes required for the addItem() method. Then map the required data from your data object to the new item object, and instead of using addItem(data) you could use addItem(item).
Add the property when setting the value:
useEffect(() => {
api.get(`/items/${params.id}`)
.then(({ data }) => {
setData({
...data,
id: data._id
});
});
}, []);

Why this function doesn't rendering in react

first, Thank you for entering like here
i want to be able to use like this code, this code is rendering in react component but second code doesn't work ..
That's what i worder sir.
function forFourMultiplyFour(_pictures) {
if (_pictures.length === 0) {
return '';
}
return <div
style={{ display: 'flex', justifyContent: 'center' }}>{_pictures.map((el) => {
return <div style={{ margin: '5px' }}>
<Img key={el.id} src={el.src} alt="picture"></Img>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>{el.title}</div>
<div>생성일자</div>
</div>
</div>;
})}</div>;
}
function makeHowManyPage(count) {
// 태그안에 함수로 또 다른 태그를 감싼다음에 forFourMultiplyFour로 한 것처럼 렌더링할 것을 return 했는데
// 안되서 state을 배열로 만들어서
return <div
className="makeHowManyPage"
style={{ display: 'flex', justifyContent: 'center' }}>
{() => {
for (let i = 1; i <= count; i++) {
return <div>{i}</div>
}
}}
</div>
}
and then i do render like this
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import dummyPictures from '../../../dummyDate';
function Gallery() {
const [forRenderingOne, setForRenderingOne] = useState(<div></div>);
const [forRenderingTwo, setForRenderingTwo] = useState(<div></div>);
const [forRenderingThree, setForRenderingThree] = useState(<div></div>);
const [forRenderingFour, setForRenderingFour] = useState(<div></div>);
const [pageCount, setPageCount] = useState(<div>1</div>);
const [_temp, set_Temp] = useState(['안녕하세요', '안녕하세요', '안녕하세요', '안녕하세요'])
// 애초에 4개씩 받아서 뿌릴 것
function forFourMultiplyFour(_pictures) {
if (_pictures.length === 0) {
return '';
}
return <div
style={{ display: 'flex', justifyContent: 'center' }}>{_pictures.map((el) => {
return <div style={{ margin: '5px' }}>
<Img key={el.id} src={el.src} alt="picture"></Img>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>{el.title}</div>
<div>생성일자</div>
</div>
</div>;
})}</div>;
}
function makeHowManyPage(count) {
// 태그안에 함수로 또 다른 태그를 감싼다음에 forFourMultiplyFour로 한 것처럼 렌더링할 것을 return 했는데
// 안되서 state을 배열로 만들어서
return <div
className="makeHowManyPage"
style={{ display: 'flex', justifyContent: 'center' }}>
{() => {
for (let i = 1; i <= count; i++) {
return <div>{i}</div>
}
}}
</div>
}
useEffect(() => {
// 서버에서 줄때 무조건 객체 16개가 든 배열이 응답해와야 정상작동되는 코드다..
setPageCount(makeHowManyPage(5))
setForRenderingOne(forFourMultiplyFour(dummyPictures.pictures.slice(0, 4)));
setForRenderingTwo(forFourMultiplyFour(dummyPictures.pictures.slice(4, 8)));
setForRenderingThree(forFourMultiplyFour(dummyPictures.pictures.slice(8, 12)));
setForRenderingFour(forFourMultiplyFour(dummyPictures.pictures.slice(12)));
}, []);
return (
<div>
{/* {forRenderingOne}
{forRenderingTwo}
{forRenderingThree}
{forRenderingFour} */}
{()=>{ return <div>'안녕하세요'</div>}}
</div>
)
}
export default Gallery
const Img = styled.img`
width: 15vw;
height: 20vh;
`
As stated this code snippet not working,
function makeHowManyPage(count) {
// 태그안에 함수로 또 다른 태그를 감싼다음에 forFourMultiplyFour로 한 것처럼 렌더링할 것을 return 했는데
// 안되서 state을 배열로 만들어서
return <div
className="makeHowManyPage"
style={{ display: 'flex', justifyContent: 'center' }}>
{() => {
for (let i = 1; i <= count; i++) {
return <div>{i}</div>
}
}}
</div>
}
Have a look at this snippet returning div not array,
for (let i = 1; i <= count; i++) {
return <div>{i}</div>
}
map works differently.
Consider changing this to,
function makeHowManyPage(count) {
// 태그안에 함수로 또 다른 태그를 감싼다음에 forFourMultiplyFour로 한 것처럼 렌더링할 것을 return 했는데
// 안되서 state을 배열로 만들어서
let array = [];
for (let i = 1; i <= count; i++) {
array.push(<div key={i}>{i}</div>);
}
return (
<div
className="makeHowManyPage"
style={{ display: "flex", justifyContent: "center" }}
>
{array}
</div>
);
}
Then usage,
export default function App() {
// declaration
let elements = makeHowManyPage(5);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{elements}
</div>
);
}
I don't understand what you exactly wanted, I just write some code that I expected to solve your problem.
I hope it can help your problem though it is not the best solution.
First, I divide Gallery component into two components including your forFourMultiplyFour function.
(I don't have dummy pictures, so I used a picture list api for this.)
Second, as windowsill's comment, write simple value for the useState initial value. I think you only need two values, pages and images array.
Usually people get the image list changing page and offset value not slicing image array. (If I have a wrong idea, please let me know.)
So I just change page state when clicking button.
For 4*4 image arrangement, I used flex property.
import React, { useState, useEffect } from "react";
import axios from "axios";
import ForFourMultiplyFour from "./ForFourMultiplyFour";
function Gallery() {
const [page, setPage] = useState(1);
const [images, setImages] = useState([]);
const URL = "https://picsum.photos/v2/list";
const LIMIT = 16;
useEffect(() => {
async function fetchImages() {
const { data } = await axios.get(`${URL}?page=${page}&limit=${LIMIT}`);
setImages(data);
}
fetchImages();
}, [images, page]);
useEffect(() => {
setPage(page)
}, [page])
return <ForFourMultiplyFour images={images} page={page} setPage={setPage} />;
}
export default Gallery;
import styled from "styled-components";
function ForFourMultiplyFour({ images, setPage, page }) {
const handlePage = (param) => {
if (param === "plus") {
setPage(page + 1);
} else {
if (page === 1) return;
setPage(page - 1);
}
};
if (images.length < 1) return <></>;
return (
<>
<ButtonWrapper>
<Button onClick={() => handlePage("minus")}>prev</Button>
<Button onClick={() => handlePage("plus")}>next</Button>
</ButtonWrapper>
<div
style={{
display: "flex",
justifyContent: "center",
flexWrap: "wrap"
}}
>
{images.map((el) => {
return (
<div style={{ margin: "5px", flex: "1 1 20%" }} key={el.id}>
<Img key={el.id} src={el.download_url} alt="picture"></Img>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<div>생성일자</div>
</div>
</div>
);
})}
</div>
</>
);
}
const Img = styled.img`
width: 15vw;
height: 20vh;
`;
const ButtonWrapper = styled.div`
display: flex;
`;
const Button = styled.button`
cursor: pointer;
`;
export default ForFourMultiplyFour;
You can see the result through this Code Sandbox link.
If it is not a solution that you wanted, please comment me. Then I'll try to help you using another solution.

react-spring and react-intersection-observer - tons of rerenders

JSFiddle
Code:
export default function App() {
const spring = useSpring({ from: { opacity: 0 }, to: { opacity: 1 } });
const [ref] = useInView();
rerenders++;
return (
<div style={{ height: "200vh" }}>
<div style={{ height: "150vh" }}></div>
<animated.div
ref={ref}
style={{
height: "50px",
width: "50px",
backgroundColor: "red",
opacity: spring.opacity
}}
>
Hello!
</animated.div>
</div>
);
}
Attaching useInView's ref (a hook from react-intersection-observer) causes constant rerendering of the component. Why is that so?
Using an IntersectionObserver yourself does not do such a thing:
const ref = useRef<any>();
useLayoutEffect(() => {
const obs = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
console.log(entry);
});
});
obs.observe(ref.current);
}, []);

React InfiniteScroll in a scrollable component on the page

I am trying to build an infinite scroll in a div with a fixed height and a scroll attached to it, so my goal is for the window not to move but a component within to have a scroll and the items within to be added infinatly.
this is what i have so far:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import InfiniteScroll from "react-infinite-scroll-component";
const style = {
height: 18,
border: "1px solid green",
margin: 6,
padding: 8
};
const DoseListCardBody = () => {
const [items, setItems] = useState(Array.from({ length: 20 }));
const fetchMoreData = () => {
setItems(items.concat(Array.from({ length: 10 })));
};
return (
<div style={{ height: "100%", overflowY: "scroll" }}>
<InfiniteScroll
dataLength={items.length}
next={fetchMoreData}
hasMore={items.length < 200}
loader={<h4>Loading...</h4>}
>
{items.map((i, index) => (
<div style={style} key={index}>
div - #{index}
</div>
))}
</InfiniteScroll>
</div>
);
};
ReactDOM.render(
<div style={{ height: "35rem", background: "black" }}>
<div style={{ height: "30rem", background: "white" }}>
<DoseListCardBody />
</div>
</div>,
document.getElementById("root")
);
everything works fine if i change
ReactDOM.render(
<div style={{ height: "35rem", background: "black" }}>
<div style={{ height: "30rem", background: "white" }}>
<DoseListCardBody />
</div>
</div>,
document.getElementById("root")
);
to
ReactDOM.render(
<DoseListCardBody />,
document.getElementById("root")
);
I think this is because it is using the scroll of the window not the component.
How do i get InfiniteScroll to use the parent component or a component with a scroll that I specify.
I appologise for the bad terminology, i dont usualy develop web pages.
ok got it!
one must use scrollableTarget as a prop in the InfiniteScroll and specify the ID of the compnent that has the scrollbar.
example:
const DoseListCardBody = () => {
const [items, setItems] = useState(Array.from({ length: 20 }));
const fetchMoreData = () => {
setItems(items.concat(Array.from({ length: 10 })));
};
return (
<div id="scrollableDiv" style={{ height: "100%", overflowY: "scroll" }}>
<InfiniteScroll
dataLength={items.length}
next={fetchMoreData}
hasMore={items.length < 200}
loader={<h4>Loading...</h4>}
scrollableTarget="scrollableDiv"
>
{items.map((i, index) => (
<div style={style} key={index}>
div - #{index}
</div>
))}
</InfiniteScroll>
</div>
);
};
notice the addition of 'id="scrollableDiv"' and 'scrollableTarget="scrollableDiv"'.

Why the value of clientHeight of <li> element does not change when scrolling?

I am trying to hide the <li></li> element when it's overflowing (while scrolling, and just before scrolling after adding some products to the shopping cart), however, the clientHeight on the overflowingRef, which refers to the <li></li> element does not change when its content is overflowing. Any idea what i m doing wrong?
Please see the code for reference below:
import React, {useState, useRef, useEffect} from 'react';
import products from './mock.json';
export default function App() {
return (
<div style={{display: 'flex', flexDirection: 'row'}}>
<ShoppingCart products={products} />
</div>
);
}
function List({products, title, addProduct, removeProduct}) {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'start-end',
}}
>
{title}
<ul
style={{
listStyle: 'none',
paddingInlineStart: '0px',
width: '500px',
height: '260px',
overflow: 'scroll',
paddingRight: '10px',
}}
>
{products.length === 0 ? (
<div>No products</div>
) : (
products.map(({category, price, stocked, name}, index) => (
<SuspendDisplayIfOverflowing>
{ref => (
<li
style={{
padding: '10px',
border: '1px solid blue',
display: 'flex',
justifyContent: 'space-between',
height: '18px',
overflow: 'scroll',
}}
ref={ref}
key={price}
>
<div style={{color: `${stocked ? 'green' : 'red'}`}}>
{`${name} - ${category} - available: ${stocked} - price: ${price}`}
</div>
{addProduct && stocked ? (
<button
onClick={() =>
addProduct({category, price, stocked, name})
}
>
Add
</button>
) : null}
{removeProduct ? (
<button onClick={() => removeProduct(index)}>Remove</button>
) : null}
</li>
)}
</SuspendDisplayIfOverflowing>
))
)}
</ul>
<Total prices={products.map(({price}) => getCleanPrice(price))} />
</div>
);
}
function ShoppingCart({products}) {
const [productsInCart, manipulateProduct] = useState([]);
const addProduct = product =>
manipulateProduct(productsInCart.concat(product));
const removeProduct = productIndex =>
manipulateProduct(
productsInCart.filter((_, index) => index !== productIndex),
);
return (
<>
<List
title="Available Products"
products={products}
addProduct={addProduct}
/>
<List
title="Shopping Cart"
products={productsInCart}
removeProduct={removeProduct}
/>
</>
);
}
function Total({prices}) {
const total = prices.reduce((accum, item) => (accum += parseFloat(item)), 0);
return <div>Total amount to pay: {total.toFixed(2)}</div>;
}
function getCleanPrice(price) {
return price.slice(1);
}
function SuspendDisplayIfOverflowing(props) {
const overflowingRef = useRef(null);
useEffect(() => {
console.log(
'oveflowing scroll height',
overflowingRef.current.scrollHeight,
);
console.log(
'oveflowing client height',
overflowingRef.current.clientHeight,
);
console.log(
'oveflowing client height',
overflowingRef.current.offsetHeight,
);
console.log(overflowingRef.current);
}, [overflowingRef]);
return props.children(overflowingRef);
}

Resources