How Could I Change The State When Scroll on ReactJS? - reactjs

I want to change the state when scrolling. Here, the default value of the background is false. I want when scroll the page the value will be true and then I will change the className dynamically using the ternary operator. But it isn't working. please give me a solution using onScoll method and the background state will be changed when scroll the page.
const Header = () => {
const [background, setBackground] = useState(false)
return (
<div onScroll={() => setBackground(true)} >
<div >
<div className={background ?
'nav bg-error w-[90%] py-5 mx-auto text-[white] flex justify-between '
:
'nav w-[90%] py-5 mx-auto text-[white] flex justify-between'}>
<div>
<p> logo</p>
</div>
<div>
<ul className='flex justify-around'>
<li>item1</li>
<li className='mx-10'>item2</li>
<li className='mr-10'>item3</li>
<li>item4</li>
</ul>
</div>
</div>
</div>
<div className=' text-[white]'>
<img src="https://img.freepik.com/free-photo/close-up-islamic-new-year-concept_23-2148611670.jpg?w=1380&t=st=1655822165~exp=1655822765~hmac=c5954765a3375adc1b56f2896de7ea8a604cd1fb725e53770c7ecd8a05821a60" alt="" />
</div>
</div>
);
};
export default Header;

That's because you need to set overflow:scroll to the div. Otherwise the onScroll prop won't work.
But remember that using the solution above will render unwanted extra scrollbars.
So you can try this instead:
const [background, setBackground] = React.useState(false);
React.useEffect(() => {
window.addEventListener("scroll", (e) => setBackground(true));
return () => {
window.removeEventListener("scroll", (e) => setBackground(false));
};
}, [background]);

Related

React is complaining that I'm causing too many re-renders

The renderDialogue function should render Dialogue when the window size is less than 654px, otherwise it should update the state of expandMenu to false. However, React is saying there are too many re-renders. How to solve this?
const [expandMenu, setExpandMenu] = useState(false);
const handleOpenMenu = () => {
setExpandMenu(!expandMenu);
};
const renderDialogue = () => {
if (window.innerWidth < 654) {
return (
<Dialog
open={expandMenu}
handler={() => handleOpenMenu()}
size={"xl"}
className={"bg-transparent shadow-none "}
>
<DialogBody>
<div className={"relative"}>
<img
src={"/imgFolder2/cloud.webp"}
className={"h-full float-center"}
/>
<ul className="flex flex-col justify-center h-[75%] gap-5 text-2xl text-center absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] w-[55%]">
{NavItems()}
</ul>
</div>
</DialogBody>
</Dialog>
);
} else {
setExpandMenu(false);
return <> </>;
}
};
Here is the NavItems function, which iterates through an array of links.
const NavItems = () => {
return paths.map((path, idx) => (
<li key={idx}>
<Link
href={getPath(idx)}
className={`text-black block ${
isSelectedPath(getName(idx, true))
? "border-b-4 border-buttonBG"
: ""
}`}
onClick={() => {
if (window.innerWidth < 654) setExpandMenu(!expandMenu);
}}
>
{getName(idx)}
</Link>
</li>
));
};
First of all, your component will never re-render when the window size changes. This means that your if-statement where you display something depending on the window width will only fire on first render. Which probably is fine, but not recommended.
Secondly, your error "too many re-renders" is because you invoke setExpandMenu directly in your component. Each time you update your expandMenu state, this component will re-render. Then you update it again, so it will re-render again. See the infinite loop here?
Below is a working example of what you want, including updating on window resize. I've added comments to explain what is happening:
const [expandMenu, setExpandMenu] = useState(false);
const [windowWidth, setWindowWidth] = useState(0)
useEffect(() => { // This will update your state depending on your window size
function updateSize() {
setWindowWidth(window.innerWidth)
}
window.addEventListener('resize', updateSize);
updateSize();
return () => window.removeEventListener('resize', updateSize);
}, []);
const handleOpenMenu = () => {
setExpandMenu(!expandMenu);
};
const renderDialogue = () => {
if (windowWidth > 654 || expandMenu) {
return (
<Dialog
open={expandMenu}
handler={() => handleOpenMenu()}
size={"xl"}
className={"bg-transparent shadow-none "}
>
<DialogBody>
<div className={"relative"}>
<img
src={"/imgFolder2/cloud.webp"}
className={"h-full float-center"}
/>
<ul className="flex flex-col justify-center h-[75%] gap-5 text-2xl text-center absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] w-[55%]">
{NavItems()}
</ul>
</div>
</DialogBody>
</Dialog>
);
} else {
// setExpandMenu(false); <--- This line is causing rerenders: Remove it!
return <> </>;
}

How can I display the button only once?

I'm passing all the user's data to the card component, but I want to remove the card when I click the button, without rendering the button more than one time. Is it possible to achieve that?
The cards are stacked on top of each other.
Thanks in advance!
This is where I'm getting the data and controlling the button click
const [user, setUser] = React.useState(null)
const [selectedUser, setSlectedUser] = React.useState(0)
const getUsers = async () => {
try{
const response = await axios.get('http://localhost:8000/users')
setUser(response.data)
console.log(response.data)
}
catch(err){
console.log(err)
}
}
useEffect(()=>{
getUsers()
}, [])
const handleCardClick = (userId) => {
setSlectedUser(userId)
}
const handleRemove = () => {
setUser(user.filter((user)=> user.userId !== selectedUser))
}
And this is where I'm rendering it.
<div>
{user && user.map(user => (
<div>
<Card
country={user.country}
name={user.name}
about={user.about}
photo={user.photo}
onClick={() => handleCardClick(user.userId)}/>
</div>
))}
<button className='btn-cards text-center' onClick={handleRemove}>DELETE</button>
</div>
This is the card component
import React from 'react'
const Card = ({name, about, photo, country}) => {
return (
//create a card to display the user's profile
<div className='bg-white rounded-3xl shadow-lg p-6 mb-4 card'>
<div className='flex flex-row justify-between'>
<div className='flex flex-col'>
<img className='rounded-2xl w-96 h-96 object-cover' src={photo} alt="" />
<h1 className='text-2xl'>{name}</h1>
<h1 className='text-xl'>{country}</h1>
<h2 className='text-xl'>{about}</h2>
</div>
</div>
</div>
)
}
export default Card
The state:
// In this stae var you will save the selected user ID
const [selectedUser, setSlectedUser] = useState(0)
The handlers:
const handleCardClick = (userId) => {
setSlectedUser(userId)
}
const handleRemove = () => {
setUser(user.filter((user)=> user.userId !== selectedUser))
}
The card item inside the list:
<Card
country={user.country}
name={user.name}
about={user.about}
photo={user.photo}
onClick={() => handleCardClick(user.userId)}/>
The button, in whatever place you like:
<button className='btn-cards text-center' onClick={handleRemove}>DELETE</button>
By the way your root 'div' in the list needs a key, I suggest to use the user's id: <div key={user.userId}>
Card component receiving the onClick method as a props:
const Card = ({name, about, photo, country, onClick}) => {
return (
//create a card to display the user's profile
<div className='bg-white rounded-3xl shadow-lg p-6 mb-4 card' onClick={onClick}>
<div className='flex flex-row justify-between'>
<div className='flex flex-col'>
<img className='rounded-2xl w-96 h-96 object-cover' src={photo} alt="" />
<h1 className='text-2xl'>{name}</h1>
<h1 className='text-xl'>{country}</h1>
<h2 className='text-xl'>{about}</h2>
</div>
</div>
</div>
)
}

How can I use ref for another component?

So I have a sidebar with list of items, and when I click on an item, it should scroll to a certain div, which are outside components. Sidebar looks like this:
Sidebar component:
const Sidebar = () => {
const [sideBar, setSidebar] = useState(false);
return (
<div className="sidebar">
<span class="btn" onClick={() => setSidebar(!sideBar)}>Menu</span>
<div className="profile">
<img src={spike}/>
<span>Alim Budaev</span>
<span>Available for work</span>
</div>
<ul className="sidebarlist" id={sideBar ? "hidden" : ""}>
{SlidebarData.map((val,key) =>{
return (
<li
className="row"
id={window.location.pathname === val.link ? "active" : ""}
key={key}
onClick={()=> {
}}>
{""}
<div>
{val.title}
</div>
</li>
);
})}
</ul>
</div>
);
}
Components in App.js:
function App() {
return (
<div className="App">
<div className="header">
<Sidebar/>
<Hero">
<Particles/>
</Hero>
<About/>
<Service/>
<Form/>
<Footer/>
</div>
</div>
);
}
So I'm looking to way to scroll to a certain component when I click on . I know it can be made through useRef(), but I don't know how to do it in the Sidebar with outside components.

How to add custom arrow buttons to Alice-Carousel?

I am making a carousel component with alice-carousel (https://github.com/maxmarinich/react-alice-carousel/blob/master/README.md) but having trouble customising the arrows. Code as follows
export const Carousel: React.FC<CarouselProps> = ({text }) => {
const [activeIndex, setActiveIndex] = useState(0);
const items = ["item1","item2","item3"]
const slidePrev = () => {
activeIndex==0?(
setActiveIndex(items.length-1)):
setActiveIndex(activeIndex - 2);
};
const slideNext = () => {
activeIndex==items.length-1?(
setActiveIndex(0))
: setActiveIndex(activeIndex + 2)
};
return(
<div className="grid grid-cols-3">
<div className="col-span-2>
{text}
</div>
<div className="flex justify-end">
<button className="px-8" onClick={slidePrev}><ArrowL/></button>
<button className="px-8" onClick={slideNext}><ArrowR/></button>
</div>
<div className="col-span-3 px-10">
<AliceCarousel
mouseTracking
disableDotsControls
disableButtonsControls
activeIndex={activeIndex}
items={items}
responsive={responsive}
controlsStrategy="responsive"
autoPlay={true}
autoPlayInterval={5000}
infinite={true}
keyboardNavigation={true}
/>
</div>
</div>
)}
Above code changes the activeIndex therefore changing the items display order but it does so without the "slide" animation. I've looked at examples provided in the library used however cant seem to get it to slide smoothly. What am I doing wrong?
I encourage you to use the library's options to reduce the complexity of your implementation and stop unwanted behavior.
According to the documentation, there are two functions renderPrevButton and renderNextButton with the AliceCarousel to render your custom component (any elements, icons, buttons, ...) for the Prev and Next item on the gallery.
So, instead of defining a custom button with a custom action handler, pass your desired component to the mentioned function and give them some styles for customization.
export const Carousel: React.FC<CarouselProps> = ({text}) => {
const items = ["item1","item2","item3"]
return (
<div className="grid grid-cols-3">
<div className="col-span-2"> // ---> you forgot to add closing string quotation mark
{text}
</div>
<div className="col-span-3 px-10">
<AliceCarousel
mouseTracking
disableDotsControls
// disableButtonsControls // ---> also remove this
// activeIndex={activeIndex} // ---> no need to this anymore
items={items}
responsive={responsive}
controlsStrategy="responsive"
autoPlay={true}
autoPlayInterval={5000}
infinite={true}
keyboardNavigation={true}
renderPrevButton={() => {
return <p className="p-4 absolute left-0 top-0">Previous Item</p>
}}
renderNextButton={() => {
return <p className="p-4 absolute right-0 top-0">Next Item</p>
}}
/>
</div>
</div>
)
}
Note: you need to remove the disableButtonsControls option from the AliceCarousel to handle the custom buttons properly. also, you don't need to use the activeIndex option anymore since the carousel will handle them automatically.
As a sample, I passed a p element with my renderPrevButton without any onClick action. you can define your custom icon, image, or any element and passed them.
Hi for the renderNextButton/renderPrevButton you have to declare a function first, then pass that function to the render option of the Alice Carousel.
import ArrowBackIosIcon from '#mui/icons-material/ArrowBackIos';
import ArrowForwardIosIcon from '#mui/icons-material/ArrowForwardIos';
export const Carousel: React.FC<CarouselProps> = ({text}) => {
const items = ["item1","item2","item3"]
const renderNextButton = ({ isDisabled }) => {
return <ArrowForwardIosIcon style={{ position: "absolute", right: 0, top: 0 }} />
};
const renderPrevButton = ({ isDisabled }) => {
return <ArrowBackIosIcon style={{ position: "absolute", left: 0, top: 0 }} />
};
return (
<div className="grid grid-cols-3">
<div className="col-span-2"> // ---> you forgot to add closing string quotation mark
{text}
</div>
<div className="col-span-3 px-10">
<AliceCarousel
mouseTracking
disableDotsControls
// disableButtonsControls // ---> also remove this
// activeIndex={activeIndex} // ---> no need to this anymore
items={items}
responsive={responsive}
controlsStrategy="responsive"
autoPlay={true}
autoPlayInterval={5000}
infinite={true}
keyboardNavigation={true}
renderPrevButton={renderPrevButton}
renderNextButton={renderNextButton}
/>
</div>
</div>
)
}

hover over cards with React using the useref hook

I have used a useRef hook so that when I mouseover to particular card then the opacity of the other card becomes 0.4 I have a figure out a solution to this but I am thinking this might not be best solution and its quite lengthy too. Feel free to recommend me best solution regarding this. Here is my code and i have used bootstrap to create the card.
import React, { useRef } from 'react'
export default function Cardss() {
const cardOne = useRef();
const cardTwo = useRef();
const cardThree = useRef();
const mouseOverOpacityForCardOne = (e) => {
cardTwo.current.style.opacity = "0.4";
cardThree.current.style.opacity = "0.4";
}
const mouseOutOpacityForCardOne = (e) => {
cardTwo.current.style.opacity = "1";
cardThree.current.style.opacity = "1";
}
const mouseOverOpacityForCardTwo = (e) => {
cardOne.current.style.opacity = "0.4";
cardThree.current.style.opacity = "0.4";
}
const mouseOutOpacityForCardTwo = (e) => {
cardOne.current.style.opacity = "1";
cardThree.current.style.opacity = "1";
}
const mouseOverOpacityForCardThree = (e) => {
cardOne.current.style.opacity = "0.4";
cardTwo.current.style.opacity = "0.4";
}
const mouseOutOpacityForCardThree = (e) => {
cardOne.current.style.opacity = "1";
cardTwo.current.style.opacity = "1";
}
return (
<section className="container-fluid section-three">
<h2 className="display-3">Projects</h2>
<div className="row">
<div ref={cardOne} onMouseOver={mouseOverOpacityForCardOne} onMouseOut={mouseOutOpacityForCardOne} className={"col-md-4 col-12 mb-5"}>
<div className="card cards">
<img className="card-img-top" src="..." alt="Card image cap"/>
<div className="card-body">
<h5 className="card-title">Special title treatment</h5>
<p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
</div>
</div>
</div>
<div ref={cardTwo} className={"col-md-4 col-12 mb-5"} onMouseOver={mouseOverOpacityForCardTwo} onMouseOut={mouseOutOpacityForCardTwo}>
<div className="card cards">
<img className="card-img-top" src="..." alt="Card image cap"/>
<div className="card-body">
<h5 className="card-title">Special title treatment</h5>
<p className="card-text">With supporting text below as a natural lead-in to additional content.</p>
</div>
</div>
</div>
<div ref={cardThree} onMouseOver={mouseOverOpacityForCardThree} onMouseOut={mouseOutOpacityForCardThree} className={"col-md-4 col-12 mb-5"}>
<div className="card cards">
<img className="card-img-top" src="..." alt="Card image cap"/>
<div className="card-body">
<h5 className="card-title">Special title treatment</h5>
<p className="card-text">With supporting text below as a natural lead-in to additional content.</p>
</div>
</div>
</div>
</div>
</section>
)
}
You can accomplish using a combination of state variables and onMouseOver and onMouseLeave props.
Essentially, when the mouse is over a card, you store its index in the state variable, then have the class of the card be dynamic such that any index not equal to the state variable gets a class that applies the opacity: 0.4 to that card.
Here's a Codepen example illustrating this. I used opacity: 0.2 instead
To make the code less lengthy, let's first turn a card into a component.
Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.
const Card = ({ // I'm using default parameters here
imageSrc = "https://source.unsplash.com/random/400x400",
title = "Special title treatment",
text = "With supporting text below as a natural lead-in to additional content.",
...props // pass the rest props to the wrapping div
}) => (
<div {...props}>
<div className="card cards">
<img className="card-img-top" src={imageSrc} alt="Unsplash Random" />
<div className="card-body">
<h5 className="card-title">{title}</h5>
<p className="card-text">{text}</p>
</div>
</div>
</div>
);
Then, to achieve the opacity change, you can track the active card (the one with mouse over) with state and apply CSS classes to style the cards:
// Cards.js
function Cards() {
const [active, setActive] = useState(-1); // I'm using -1 to indicate no active cards
const getCardClassName = index => {
if (active > -1 && index !== active) return "fadeOut";
return "";
};
return (
<section
className="container-fluid section-three"
>
<h2 className="display-3">Projects</h2>
<div className="row">
{[0, 1, 2].map(i => ( // or [...Array(3).keys()].map
<Card
key={i}
className={`col-md-4 col-12 mb-5 ${getCardClassName(i)}`}
onMouseOver={() => {
setActive(i);
}}
onMouseOut={() => {
setActive(-1);
}}
/>
))}
</div>
</section>
);
}
// style.css
.fadeOut {
opacity: 0.4;
}
Here is a working example:

Resources