hover over cards with React using the useref hook - reactjs

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:

Related

Problem with row-alignment using React & Bootstrap GRID system

Using React.JS and BootStrap grid system, I'm aiming to build the following page.
However, with the code linked at the bottom, the page looks like this (the alignment of the elements is wrong in multiple places. For example, the banner and search bar aren't aligned with the rest of the elements.
What am I doing wrong in using the BootStrap Grid System?
Before that, I'd like to specify that all the 4 MinWidget.jsx components are wrapped inside a MinWidgetCollection.jsx component. And all the elements encompased by the red border are wrapped inside a PageInformation.jsx component. The MiniWidgetCollection must take 7/12 of the page width while the BigWidget one the rest of 5/12 (with BootStrap grid having a maximum of 12 columns per row). Similarly, the first chart below must have 7/12 of width while the second chart only 5/12.
This is the current code which produces errors in alignment.
function App() {
return (
<div className='container'>
<Searchbar/>
<Banner/>
<PageInformation/>
</div>
)
}
export const Searchbar = (props) => {
return (
<div className='row mb-5' style={{border: "1px solid green"}}>
<div className='card-header p-7 w-50'>
</div>
)
export const Banner = (props) => {
return (
<div className='row'>
<div className='card mb-5 mb-xl-10'>
</div>
</div>
)
export const PageInformation = (props) => {
return (
<div style={{border: "1px solid red"}}>
<div className='row'>
<MinWidgetCollection />
<BigWidget />
</div>
<div className='row'>
<Chart newClassname='col-md-7 col-xs-12' />
<Chart newClassname='col-md-5 col-xs-12' />
</div>
</div>
)
}
export const MinWidgetCollection = (props) => {
return (
<div className='row col-md-7 col-xs-12'>
<MinWidget />
<MinWidget />
<MinWidget />
<MinWidget />
</div>
)
}
export const MinWidget = (props) => {
return (
<div className='col-md-6 col-xs-12'>
<div className='card card-md-6 card-xs-12 mb-xl-8'>
</div>
</div>
)
}
export const BigWidget = (props) => {
return (
<div className='col-md-5 col-xs-12'>
<div className="card card-xl-stretch mb-5 mb-xl-8 h-100">
</div>
</div>
)
}
export const Chart = (props) => {
<div className={props.newClassname}>
<div className={`card mt-5 `}>
</div>
</div>
}

How Could I Change The State When Scroll on 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]);

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 to pass props to a component on clicking button?

I have displayed all the anime images from api, now I want if any anime result is clicked then it should open details for that particular anime. I have created component Details where I want to send details of anime.
how can I send details of anime to the component Details ?
Here is my code:
import { useEffect, useState } from 'react';
import './CSS/style.css';
import Details from './components/Details';
function App() {
const [user, setUser] = useState([])
const getUsers = async () =>{
const response = await fetch('https://ghibliapi.herokuapp.com/films')
setUser(await response.json());
}
useEffect(() => {
getUsers();
}, []);
const getDetail = (e) =>{
// what should I write here to pass props on component < Details />
console.log(e)
}
return (
<>
<h2>Anime World</h2>
<div className="container-fluid mt-5">
<div className="row text-center">
{
user.map((curElem) => {
return (
<div className="col-10 col-md-4 mt-5" key={curElem.id} >
<div className="card">
<img src={curElem.image} className="card-img-top imageSize" alt="..."/>
<div className="card-body">
<h5 className="card-title">{curElem.title} </h5>
<button onClick={getDetail} >Get Detail</button>
</div>
</div>
</div>
)
})
}
</div>
</div>
</>
);
}
export default App;
Dummy values are given to component Details right now, I have to get correct value from props.
import React from 'react';
import './details.css';
function Details() {
return (
<>
<h2>Information of Anime</h2>
<div className="container-fluid mt-5">
<div className="row text-center">
<div className="col-10 col-md-8 mx-auto" >
<div className="card box">
<img src= '...' className="card-img-top imageSize" alt="..."/>
<div className="card-body">
<div className='details'>
<h5><span>Title:</span> Title</h5>
<h5><span>Original Title:</span> Original Title</h5>
<h5><span>Director:</span> Director</h5>
<h5><span>Producer:</span> Producer</h5>
<h5><span>Release Date:</span> date</h5>
<h5><span>Running time:</span> time</h5>
<h5><span>Description:</span> <p> Description</p></h5>
</div>
</div>
</div>
</div>
</div>
</div>
</>
)
}
export default Details;
I'm going to assume that the list you receive from the API is the list of each anime, and when you click you want to render the selected anime using the Details component. If that's the case this is my approach
import { useEffect, useState } from 'react';
import './CSS/style.css';
import Details from './components/Details';
function App() {
const [user, setUser] = useState([]);
const [anime, setAnime] = useState(null);
const getUsers = async () =>{
const response = await fetch('https://ghibliapi.herokuapp.com/films');
setUser(await response.json());
};
useEffect(() => {
getUsers();
}, []);
const getDetail = (anime) =>{
setAnime(anime);
};
return (
<>
<h2>Anime World</h2>
<div className="container-fluid mt-5">
<div className="row text-center">
{user.map((elem) => (
<div className="col-10 col-md-4 mt-5" key={curElem.id}>
<div className="card">
<img src={elem.image} className="card-img-top imageSize" alt="..."/>
<div className="card-body">
<h5 className="card-title">{elem.title} </h5>
<button onClick={() => getDetail(elem)} >Get Detail</button>
</div>
</div>
</div>
))}
</div>
</div>
{anime && <Details {...anime} />}
</>
);
}
export default App;
So basically what I'm doing here is rendering <Details /> only when an anime element is in state. If it's null it won't render the component.
Secondly I'm spreading the selected element into the Details component, so that inside the component you can access each property like this:
import React from 'react';
import './details.css';
function Details(props) {
const { title } = props; // each prop is each property of the selected anime
return (
<>
<h2>Information of Anime</h2>
<div className="container-fluid mt-5">
<div className="row text-center">
<div className="col-10 col-md-8 mx-auto" >
<div className="card box">
<img src= '...' className="card-img-top imageSize" alt="..."/>
<div className="card-body">
<div className='details'>
<h5><span>Title:</span> {title}</h5>
<h5><span>Original Title:</span> Original Title</h5>
<h5><span>Director:</span> Director</h5>
<h5><span>Producer:</span> Producer</h5>
<h5><span>Release Date:</span> date</h5>
<h5><span>Running time:</span> time</h5>
<h5><span>Description:</span> <p> Description</p></h5>
</div>
</div>
</div>
</div>
</div>
</div>
</>
)
}
export default Details;

Unexpected token when using map

While fetching data from an API and rendering it on the screen i am using map method to cycle through the array, but somehow getting 'Unexpected token' in the token and I am unable to figure out what is the issue.
Getting an error where the map method is defined.
const ShopItems = props => {
return (
{
props.myData.map( (card) => (
<div className="card">
<img className="card-img-top" src="..." alt="Card image cap" />
<div className="card-body">
<h5 className="card-title">Card title</h5>
<p className="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
Go somewhere
</div>
</div>
))
}
)
}
const App = () => {
const [item, setItem] = React.useState([])
React.useEffect(() => {
fetch('https://fakestoreapi.com/products/')
.then(res => res.json())
.then(data => {
setItem(data);
})
}, [])
return(
<div>
<Header />
<ShopItems myData={item}/>
</div>
)
}
Maybe try to enclose it in <>...</>:
return (
<>
{
props.myData.map( (card) => (
<div className="card">
<img className="card-img-top" src="..." alt="Card image cap" />
<div className="card-body">
<h5 className="card-title">Card title</h5>
<p className="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
Go somewhere
</div>
</div>
))
}
</>
)

Resources