React - how to stop div onClick from changing sibling divs - css

I have 6 div elements that I want to be able to click one of and have the className change for just the div I've clicked on. Currently, when I click on one div, ALL the div classNames change. This idea came from a vanilla JS concept that I'm trying to convert into a React component. I'm not sure where/what is going wrong, if anyone can tell me how to prevent the sibling divs' onClicks from being fired or if what I have is wrong fundamentally, I would be eternally grateful. This is what I have so far:
import React, { useState} from "react";
import { Panels } from "../../components/index";
import { data } from "../../constants/index";
import "./gallery.css";
const Gallery = () => {
const [isOpen, setIsOpen] = useState(false);
const toggleOpen = () => {
setIsOpen(!isOpen);
};
return (
<div className="panels">
{data.restaurants.map((restaurant, index) => (
<div
className={`panel panel${index} ${isOpen ? "open open-active" : ""}`}
onClick={toggleOpen}
>
<Panels
key={restaurant.name + index}
description={restaurant.description}
name={restaurant.name}
website={restaurant.website}
/>
</div>
))}
</div>
);
};
export default Gallery;
This is my Panels Component code:
import React from "react";
const Panels = ({ name, description, website }) => {
return (
<div className="panel_text">
<p>{description}</p>
<p>{name}</p>
<a href={website}>
<p>Visit {name}</p>
</a>
</div>
)};
export default Panels;
Aaaand this is my CSS code:
*, *:before, *:after {
box-sizing: inherit;
}
.panels {
min-height: 100vh;
overflow: hidden;
display: flex;
}
.panel, .panel_text {
background: '#fff';
box-shadow: inse 0 0 0 5px rgba(255, 255, 255, 0.1);
color: var(--color-golden);
text-align: center;
align-items: center;
transition:
font-size 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11),
flex 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11);
font-size: 20px;
background-size: cover;
background-position: center;
flex: 1;
justify-content: center;
display: flex;
flex-direction: column;
}
.panel0 {background-image: url(../../assets/defuegocarousel.jpg);}
.panel1 {background-image: url(../../assets/HaydenslakeSz.jpg);}
.panel2 {background-image: url(../../assets/stonecliffSz.jpg);}
.panel3 {background-image: url(../../assets/shigezooutside.png);}
.panel4 {background-image: url(../../assets/southparkSz.jpeg);}
.panel5 {background-image: url(../../assets/lechonoutside.jpg);}
.panel > * {
margin: 0;
width: 100%;
transition: transform 0.5s;
flex: 1 0 auto;
display: flex;
justify-content: center;
align-items: center;
}
.panel > *:first-child {transform: translateY(-100%);}
.panel.open-active > *:first-child {transform: translateY(0); }
.panel > *:last-child { transform: translateY(100%); }
.panel.open-active > *:last-child {transform: translateY(0); }
.panel_text p, a {
text-transform: uppercase;
}
.panel p:nth-child(2) {
font-size: 4rem;
}
.panel.open {
flex: 5;
font-size: 40px;
}
#media only screen and (max-width: 600px) {
.panel p {
font-size: 1rem;
}
}

You're saving a boolean value as a div element is open or not. So this value is considered for all div element's because there is no identifier which div element is open. You need to save a div element value to identify the open div element.
So you can use a div element's index instead of a boolean value. For example, try the below code.
import React, { useState} from "react";
import { Panels } from "../../components/index";
import { data } from "../../constants/index";
import "./gallery.css";
const Gallery = () => {
const [isOpen, setIsOpen] = useState(null);
return (
<div className="panels">
{data.restaurants.map((restaurant, index) => (
<div
className={`panel panel${index} ${isOpen === index ? "open open-active" : ""}`}
onClick={() => setIsOpen(index)}
>
<Panels
key={restaurant.name + index}
description={restaurant.description}
name={restaurant.name}
website={restaurant.website}
/>
</div>
))}
</div>
);
};
export default Gallery;

your isOpen state is common between all your div's
you should specify a unique value for isOpen of each div
you can change your isOpen state to an object like this :
import React, { useState} from "react";
import { Panels } from "../../components/index";
import { data } from "../../constants/index";
import "./gallery.css";
const Gallery = () => {
const [isOpen, setIsOpen] = useState({});
const toggleOpen = (index) => {
setIsOpen(prevState => {...prevState ,[index]:!(!!prevState[index]) });
};
return (
<div className="panels">
{data.restaurants.map((restaurant, index) => (
<div
className={`panel panel${index} ${isOpen[index] ? "open open-active" : ""}`}
onClick={()=>toggleOpen(index)}
>
<Panels
key={restaurant.name + index}
description={restaurant.description}
name={restaurant.name}
website={restaurant.website}
/>
</div>
))}
</div>
);
};
export default Gallery;

Related

CSS hover applies to child elements (absolute positioned) also however I want it to affect only the parent container

I have a header with lots of navItems, for the last navItem, I have another expander div attached which also captures the hover effect of the parent div. How can I disable the pointer-events to none for this child absolute expander?
If I set pointer event of the absolute expander to none then the elements inside of expander also lose hover effect. (Note that they're also of same hover class).
Here's my JSX (HTML like template):
import React from 'react';
import classes from './NavItems.module.css';
import navItems from '#/constants/navItems.json';
import { BiChevronDown } from 'react-icons/bi';
import { useAppDispatch, useAppSelector } from '#/redux/hooks';
import { updateActiveSlice } from '#/slices/navigation.slice';
import { motion } from 'framer-motion';
const getActiveItem = (activeItem: string) => {
const result = navItems.find((element) => element.value === activeItem);
return result;
};
const moreItems = {
value: 'more',
label: 'More',
index: -1
};
const getItems = (activeItem: string) => {
const activeElement = getActiveItem(activeItem);
if (navItems.length > 5 && activeElement) {
if (activeElement.index > 3) return [...navItems.slice(0, 4), activeElement];
return [...navItems.slice(0, 4), moreItems];
}
return navItems;
};
export default function NavItems() {
const { activeSection } = useAppSelector((state) => state.navigation);
const dispatch = useAppDispatch();
const items = getItems(activeSection);
const activeItem = getActiveItem(activeSection);
return (
<div className={classes.NavItemContainer}>
{items.map((item, index) => {
const isLastItem = index === items.length - 1;
const isActive =
(activeItem ? activeItem.value : activeSection) === item.value;
return (
<h4
key={item.value}
className={[
classes.NavItem,
isLastItem ? classes.LastItem : null,
isActive ? classes.ActiveItem : null
].join(' ')}
onClick={() => {
// TODO: verify more click
dispatch(updateActiveSlice(item.value));
}}
>
{item.label}
{isLastItem && (
<React.Fragment>
<BiChevronDown className={classes.ShowMoreIcon} />
<motion.div
initial={{ y: 50 }}
animate={{ y: [-50, 20, 0] }}
className={classes.Expander}
>
<h4 className={classes.NavItem}>Rest</h4>
<h4 className={classes.NavItem}>Next Next Next</h4>
<h4 className={classes.NavItem}>Trust</h4>
<h4 className={classes.NavItem}>Rust</h4>
</motion.div>
</React.Fragment>
)}
</h4>
);
})}
</div>
);
}
and here's my CSS:
.NavItemContainer {
display: flex;
align-items: center;
color: var(--inactive-silver);
gap: 2rem;
}
.NavItem {
letter-spacing: 0.05em;
font-family: var(--raleway);
font-style: normal;
font-weight: 600;
font-size: 1.2rem;
line-height: var(--lineheight);
cursor: pointer;
transition: all 0.1s ease-in;
}
.NavItem:hover {
scale: 0.96;
}
.LastItem {
display: flex;
align-items: center;
position: relative;
}
.ActiveItem {
color: var(--active-blue);
}
.ShowMoreIcon {
font-size: 2rem;
color: var(--inactive-silver);
}
.Expander {
position: absolute;
top: 130%;
right: 5%;
width: max-content;
padding: 0 1rem;
background-color: var(--light-background-color);
box-shadow: var(--box-shadow-thick);
text-align: end;
border-radius: 0.4rem;
}
.Expander:hover {
scale: 1;
}

scrolling for overflow-x not working properly

I can scroll on the x axis only by moving the laptop touchpad right to left or by pressing in the scroll button and then moving right to left.Not with normal scroll.
the css is the following:
.row {
color: white;
margin-left: 20px;
}
.row__posters {
display: flex;
overflow-y: hidden;
overflow-x: scroll;
padding: 20px;
}
.row__posters::-webkit-scrollbar {
display: none;
}
.row__poster {
object-fit: contain;
width : 100%;
max-height: 100px;
margin-right: 10px;
transition: transform 450ms;
}
.row__poster:hover {
transform: scale(1.08);
}
.row__posterLarge {
max-height: 250px;
}
.row__posterLarge:hover {
transform: scale(1.09);
}
the Javascipt file is:
import React,{ useState , useEffect} from 'react'
import axios from './axios';
import './Row.css';
const base_url = "https://image.tmdb.org/t/p/original/";
function Row({ title ,fetchUrl,isLargeRow }) {
const [movies, setMovies] = useState([]);
// A snippet of code which runs based on a specific condition
useEffect(() => {
// if we leave the brackets blank [] ,run once when the row loads
and dont run again
async function fetchData() {
const request = await axios.get(fetchUrl);
setMovies(request.data.results);
return request;
}
fetchData();
}, [fetchUrl]);
return (
<div className="row">
<h2>{title}</h2>
<div className="row__posters">
{/* several row_posters */}
{movies.map(movie => (
<img
key={movie.id}
className={`row__poster ${isLargeRow && "row__posterLarge"}`}
src={`${base_url}${
isLargeRow ? movie.poster_path : movie.backdrop_path
}`}
alt={movie.name}
/>
))}
</div>
</div>
)
}
export default Row
I tried alot of solutions but I must be doing something wrong because nothing worked .it could be that I used the proposed code in the wrong department.
Thank you for the help in advance!!

CSS Animation not properly working in React App

So I'm trying to make a simple dice roller and want to add a spin animation to make it look cool. For some reason, the animation only works on the initial roll, and not on any after that (unless I refresh). Here is what I have:
App.tsx
export default function App() {
const [dice, setDice] = useState<string[]>([]);
return (
<div className="container">
<h1 className="title-text">Dice Roller</h1>
<div className="roll-dice-button">
<RollBtn setDice={setDice} />
</div>
<Dice dice={dice} />
</div>
)
}
RollBtn.tsx
type Props = {
setDice: (s: string[]) => void
}
export default function RollBtn({setDice}: Props) {
const roll = () => {
let dice: string[] = []'
for(let i = 0; i < 2; i++) {
dice.push(Math.round(Math.random() * 5 + 1));
}
setDice(dice);
}
return <button onClick={() => roll()}>Roll Dice</button>;
}
Dice.tsx
type Props = {
dice: string[];
}
export default function Dice({ dice }: Props) {
return (
<div className="dice-container">
{dice.map((d, i) => (
<div className="die" key={i}>{d}</div>
))}
</div>
)
}
styles.scss
.dice-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 1em;
justify-items: center;
align-items: center;
.die {
width: 2em;
height: 2em;
background-color: white;
border-radius: 8px;
color: black;
font-weight: 900;
font-size: 2em;
line-height: 2em;
text-align: center;
animation: spin_dice .25s;
}
}
As I previously stated, on the initial roll it will do the animation, but after that, it will just change the numbers within the dice.
So, if the animation is not a loop, it will be executed just once when the component is rendered. So you need to change the class of the div .die for "" and then change it back to .die when you click on the roll button.
For that you should put the RollBtn and Dice in just one component.

How can i make slider animation?

I need to make the vertical slider animation ( dots and line ) as in this pic
i managed to do the Accordion and the dots but i don't know how i will going to implement it ( i'm using pseudo )
**my accordion component Where i define the logic of my nested accordions as in images based on array of data **
function MultiLevelAccordion({
data,
bodyClass,
headerClass,
wrapperClass,
renderHeader,
renderContent,
}) {
const RootAccordionId = 'parent-0';
const [accordionsStates, setActiveCardsIndex] = useMergeState({});
const onAccordionToggled = (id, activeEventKey) => {
console.log(activeEventKey);
setActiveCardsIndex({
[id]: activeEventKey ? Number(activeEventKey) : activeEventKey
});
};
console.log('data', data);
const accordionGenerator = (data, parentId) => {
return map(data, (item, index) => {
const active = accordionsStates[parentId] === index;
const hasChildren = item.hasOwnProperty('children') && isArray(item.children) && !isEmpty(item.children);
const isRootAccordion = RootAccordionId === parentId;
const isLastNestedAccordion = !isRootAccordion && !hasChildren;
const accordion = (
<Card className={classNames(wrapperClass, {
'nested-root-accordion': !isRootAccordion,
'last-nested-root-accordion': isLastNestedAccordion,
'multi-level-accordion': !isLastNestedAccordion
})}
>
<Accordion.Toggle
{...{ ...item.id && { id: item.id } }}
onClick={() => this}
as={Card.Header}
eventKey={`${index}`}
className={'cursor-pointer d-flex flex-column justify-content-center'}
>
<div className="d-flex justify-content-between align-items-center">
{renderHeader(item, hasChildren)}
<img
style={{
transition: 'all .5s ease-in-out',
transform: `rotate(${active ? 180 : 0}deg)`
}}
src={setIcon('arrow-down')}
className="ml-2"
alt="collapse"
/>
</div>
</Accordion.Toggle>
<Accordion.Collapse eventKey={`${index}`}>
<Card.Body
className={`accordion-content-wrapper ${!hasChildren ? 'accordion-children-body' : ''} ${bodyClass}`}
>
{!hasChildren ? renderContent(item, hasChildren) : (
<Accordion onSelect={activeEventKey => onAccordionToggled(`${parentId}-${index}`, activeEventKey)}>
<Fade cascade top when={active}>
{accordionGenerator(item.children, `${parentId}-${index}`)}
</Fade>
</Accordion>
)}
</Card.Body>
</Accordion.Collapse>
</Card>
);
return isRootAccordion ? accordion : (
<div className={'d-flex align-items-center'}>
{accordion}
<div className="accordion-indicator-wrapper">
<div className="accordion-indicator" id={`indicator-${parentId}-${index}`} />
</div>
</div>
);
});
};
if (!isArray(data)) {
return;
}
return (
<Accordion onSelect={activeEventKey => onAccordionToggled(RootAccordionId, activeEventKey)}>
{accordionGenerator(data, RootAccordionId)}
</Accordion>
);
}
export default MultiLevelAccordion;
the styles used in scss
.faqs-questions-wrapper {
padding: 20px 10px
}
.faqs-q-count {
color: $black-color;
font-size: calc(1rem - 1rem/8)
}
.faqs-q-a-wrapper {
flex-basis: 95%;
}
.faq-child-title {
color: $black-color
}
.nested-root-accordion {
flex-basis: 90%;
}
.accordion-indicator-wrapper {
flex-basis: 10%;
width: 100%;
display: flex;
justify-content: center;
.accordion-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: $theme-color;
position: relative;
}
}
Any clue?
Thanks in Advance.
React JS is gonna make this easy
The lines expansion will need to be coded based on the height of the box window
For the dropdown container keep the vertical button lines in a separate div than the Accordian
Check out this pen for creating lines between buttons
https://codepen.io/cataldie/pen/ExVGjya
css part:
.status-container{
padding:10px;
margin:10px;
position:relative;
display: inline-block;
}
.bullet{
padding:0px;
margin:0px;
display: inline-block;
z-index: 10;
}
.bullet:before {
content: ' \25CF';
font-size: 5em;
}
.bullet-before{
/*position:relative;
right:-12px;*/
}
.bullet-after{
/*position:relative;
left:-30px;*/
}
.line{
stroke:blue;
stroke-width:0.3em;
padding:0px;
margin:0px;
display: inline-block;
}
.line-on{
stroke:red;
}
.line-off{
stroke:gray;
}
.color-on{
color: red;
}
.color-off{
color: gray;
}
https://codepen.io/emwing/pen/LgzJOx
I think you can use some inspiration here

When reusing component, my CSS doesn't get applied to it

I am trying to figure out how something like this might occur. Bear with me, since the details might be a little bit sloppy.
I have a Header Component which simply takes up all the viewport, and then adds a NavigationBar Component. The Header Component works just fine in the other place I used it, but for some reason, when I tried reusing it just now, the NavigationBar inside it gets funky (all CSS is simply gone).
Here is the Header component that has the following styling (which works btw): {position: relative;
height: 100vh;
width: 100%;}
import React from "react";
import NavigationBar from "../NavigationBar/NavigationBar";
import "./Header.css";
const Header = (props) => (
<div className="blog-header">
<NavigationBar />
{props.children}
</div>
);
export default Header;
My NavigationBar is a simple React-Bootstrap Navbar (I have decided to delete what was inside navigationItems because I don't think those matter to the problem at hand):
import React from "react";
import { Container, Navbar, Nav, NavbarBrand } from "react-bootstrap";
import Logo from "../Logo/Logo";
import "./NavigationBar.css";
const navigationItems = []
const NavigationBar = (props) => (
<Container>
<Navbar id="navigation" bg="transparent" variant="dark" expand="lg">
<div className="div-brand d-flex flex-grow-1">
<NavbarBrand href="/">
<Logo />
</NavbarBrand>
<div className="w-100 text-right">
<Navbar.Toggle data-toggle="collapse" data-target="#da-navbarNav">
<span className="navbar-toggler-icon"></span>
</Navbar.Toggle>
</div>
</div>
<Navbar.Collapse
className="text-uppercase flex-grow-1 text-right"
id="da-navbarNav"
>
<Nav className="ml-auto flex-nowrap">
{navigationItems.map((navItem, index) => {
return (
<Nav.Item key={index}>
<Nav.Link
id={navItem.id ? navItem.id : null}
href={navItem.path}
className={navItem.classes.join(" ")}
onClick={(event) =>
props.navItemClick(event, window.location.pathname, navItem)
}
>
{navItem.placeholder}
</Nav.Link>
</Nav.Item>
);
})}
</Nav>
</Navbar.Collapse>
</Navbar>
</Container>
);
Navbar.css code:
#navigation {
z-index: 10;
}
#navigation .div-brand {
align-items: center;
}
.navbar-dark .navbar-nav .nav-link {
color: rgba(255, 255, 255, 0.75);
letter-spacing: 1px;
font-size: 0.95rem;
font-weight: bold;
line-height: 24px;
width: 6.4rem;
text-align: center;
}
.navbar-dark .navbar-nav .nav-link:hover,
.navbar-dark .navbar-nav .nav-link:active {
color: #da3833;
}
.navbar-dark #btn-contact {
background-color: #da3833;
border-radius: 3px;
text-align: center !important;
}
.navbar-dark #btn-contact:hover,
.navbar-dark #btn-contact:active {
color: white !important;
}
#media (max-width: 992px) {
.navbar-dark .navbar-nav .nav-link {
text-align: right;
margin-top: 0.2rem;
margin-left: auto;
}
.navbar-dark .navbar-nav .nav-item {
text-align: right;
width: 100%;
}
.navbar-toggler {
outline: none !important;
}
}
I'm currently reusing it inside this component that has as styling the following:
.article-header {
height: inherit;
width: inherit;
position: absolute;
top: 0;
}
import React, { useState, useEffect, useCallback } from "react";
import Header from "../../../components/Header/Header";
import "./ArticlePage.css";
const ArticlePage = (props) => {
const [id, setId] = useState(null);
const loadQueryParams = useCallback(() => {
setId(props.match.params.id ? props.match.params.id : null);
}, []);
useEffect(() => loadQueryParams(), [loadQueryParams]);
return (
<div>
<Header>
<div
className="article-header"
style={{ backgroundColor: "black", opacity: "0.2" }}
>
{id}
</div>
</Header>
</div>
);
};
export default ArticlePage;
If you might have an idea where this might go wrong, feel free to answer. I'm also gonna leave here how the navigationbar should look, and how it rendered.
If you need any more info or details please announce me! Thank you!
EDIT: as requested, here is a demo
I managed to solve it. The problem was that my "bootstrap.css" folder that contains my bootstrap theme was not being imported globally in "index.js", and instead it was inside "index.html".
To be noted: I was also using a Router to go to this new Component in which I was using Navbar, and since the css files weren't imported globally, the css wasn't there.

Resources