React prevent css animation restarting every state change - css

I have two child components, an image gallery and a hidden components which will display the clicked image of the gallery at full size.
const [selectedIndex, setSelectedtIndex] = useState(0);
const [galleryVisible, setGalleryVisible] = useState(false);
const FirstComponent = () => {
return (
<div className={'gallery-container'}>
<div class='img fade-1' onClick={() => handleClick(1)}>Image 1</div>
<div class='img fade-2' onClick={() => handleClick(2)}>Image 2</div>
<div class='img fade-3' onClick={() => handleClick(3)}>Image 3</div>
[...]
</div>
)
}
const handleClick = (index) => {
setSelectedtIndex(index)
setGalleryVisible(true)
}
const SecondComponent = ({ index }) => {
return (
<div className={`selected-img`}>Selected : {index} (But the fading animation shouldn't restart è_é)</div>
)
}
return (
<>
<FirstComponent/>
{galleryVisible &&
<SecondComponent index={selectedIndex} />
}
</>
)
The issue is that I also have a fade-in animation on the first component, and every time I click on an image to display the second component, that animation resets due to the rerender of react on state change.
&.fade-1 {
animation-delay: 0s
}
&.fade-2 {
animation-delay: 0.5s
}
&.fade-3 {
animation-delay: 1s
}
I don't know how can I only change the second component when the state is changed from a click on the first one... I tried to play with useMemo but couldn't get it to work.
Here is a codepen reproducing the issue : https://codepen.io/disgallion/pen/zYjqPBE

Move the first component outside of App and pass handleClick to its props:
const FirstComponent = ({handleClick}) => {
return (
<div className={'gallery-container'}>
<div class='img fade-1' onClick={() => handleClick(1)}>1</div>
<div class='img fade-2' onClick={() => handleClick(2)}>2</div>
<div class='img fade-3' onClick={() => handleClick(3)}>3</div>
</div>
)
}
const App = () => {
const [selectedIndex, setSelectedtIndex] = useState(0);
const [galleryVisible, setGalleryVisible] = useState(false);
const handleClick = (index) => {
console.log('click')
setSelectedtIndex(index)
setGalleryVisible(true)
}
const SecondComponent = ({ index }) => {
return (
<div className={`selected-img`}>Selected : {index} (But the fading animation shouldn't restart è_é)</div>
)
}
return (
<>
Click on an image to open the fullsize gallery:
<FirstComponent handleClick={handleClick} />
{galleryVisible &&
<SecondComponent index={selectedIndex} />
}
</>
)
}
ReactDOM.render(<App />,
document.getElementById("root"))
Now the state change of App will not trigger a re-render in FirstComponent

Related

Why carousel images are flickering in React

I have added carousel functionality but the images are flickering whenever I click on next icon. It seems like I get the reflection of the previous image or the next one as I have only two images. I'm using tailwind with react. I'm adding a gif below so one can see the problem. I'm getting flickering every time. But my screen recorder shows it only the first time.
Link to Gif
Index.css
.slide-in-from-right {
animation: slideInFromRight 0.5s ease-in;
}
.slide-out-to-left {
animation: slideOutToLeft 0.5s ease-in;
}
#keyframes slideInFromRight {
from {
transform: translateX(100%);
}
to {
transform: translateX(0%);
}
}
#keyframes slideOutToLeft {
from {
transform: translateX(0%);
}
to {
transform: translateX(-100%);
}
}
Hero SlideShow
let count = 0;
export default function HeroSlideshow() {
const [slide, setSlide] = useState({});
const [clonedSlide, setClonedSlide] = useState({});
const [slides, setSlides] = useState([]);
const [currentIndex, setCurrentIndex] = useState(0);
const slideRef = useRef();
const clonedSlideRef = useRef();
const { updateNotification } = useNotification();
const fetchLatestUploads = async () => {
const { error, movies } = await getLatestUploads();
if (error) return updateNotification("error", error);
setSlides([...movies]);
setSlide(movies[0]);
};
//0,1,2,3,4
const handleOnNextClick = () => {
setClonedSlide(slides[count]);
clonedSlideRef.current.classList.add("slide-out-to-left");
count = (count + 1) % slides.length;
setSlide(slides[count]);
setCurrentIndex(count);
slideRef.current.classList.add("slide-in-from-right");
};
const handleAnimationEnd = () => {
slideRef.current.classList.remove("slide-in-from-right");
clonedSlideRef.current.classList.remove("slide-out-to-left");
setClonedSlide({});
};
useEffect(() => {
fetchLatestUploads();
}, []);
return (
<div className="w-full flex">
{/* Slide show section */}
<div className="w-4/5 aspect-video relative overflow-hidden">
<img
ref={slideRef}
onAnimationEnd={handleAnimationEnd}
className="aspect-video object-cover"
src={slide.poster}
alt=""
/>
<img
ref={clonedSlideRef}
onAnimationEnd={handleAnimationEnd}
className="aspect-video object-cover absolute inset-0"
src={clonedSlide.poster}
alt=""
/>
<SlideShowController onNextClick={handleOnNextClick} />
</div>
{/* Up Next Section */}
<div className="w-1/5 aspect-video bg-red-300"></div>
</div>
);
}
const SlideShowController = ({ onNextClick, onPrevClick }) => {
const btnClass =
"bg-primary rounded border-2 text-white text-xl p-2 outline-none";
return (
<div className="absolute top-1/2 -translate-y-1/2 w-full flex items-center justify-between px-2">
<button onClick={onPrevClick} className={btnClass} type="button">
<AiOutlineDoubleLeft />
</button>
<button onClick={onNextClick} className={btnClass} type="button">
<AiOutlineDoubleRight />
</button>
</div>
);
};
It's difficult to answer if I cannot reproduce the issue but I have a clue of what's going on. When you call the method to handleOnNextClick you must take on mind that the slide's index (which is actually showing it as selected)'ll change almost instantly but the animation slideInFromRight and slideOutToLeft is taking .5 seconds to complete, so you can try to trigger the slide's index change with a setTimeout.
const handleOnNextClick = () => {
clonedSlideRef.current.classList.add("slide-out-to-left");
slideRef.current.classList.add("slide-in-from-right");
count = (count + 1) % slides.length;
setTimeout(() => {
setClonedSlide(slides[count]);
setSlide(slides[count]);
setCurrentIndex(count);
}, 500)
};
Hope it helps!

Why clientHeight or offsetHeight not available on ref element on toggled div in React component with useRef hook

I have a React component which show and hide some content.
Here is working example.
const Collapse = ({ children }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const [height, setHeight] = useState<number | undefined>(0);
const toggledHeight = isOpen ? height : 0;
const myRef = useRef<HTMLDivElement>(null);
const toggle = () => setIsOpen(!isOpen);
useEffect(() => {
setHeight(myRef.current?.clientHeight);
}, [myRef]);
// useLayoutEffect(() => {
// setHeight(myRef.current?.clientHeight);
// }, []);
return (
<React.Fragment>
<div
ref={myRef}
className={`content ${isOpen ? "isOpen" : ""}`}
style={{ height: `${toggledHeight}px` }}
>
{children}
{console.log(height)}
{console.log(myRef)}
</div>
<button type="button" onClick={toggle}>
<span>Toggle content</span>
</button>
</React.Fragment>
);
};
export default Collapse;
I want to create a css height transition when showing and hiding the content.
Why is myRef.current?.offsetHeight or myRef.current?.clientHeight not available and always 0?
In the Style you are passing the dynamic height. I think it's not getting in the pixels. So you need to change from
style={{ height: ${height+"px"} }}
Looks like you are telling it from the first moment to be 0 :)
No need to look for exact height number, just animate max-height
I've managed to animate you code like this:
const Collapse = ({ children }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => {
setIsOpen(!isOpen);
};
return (
<>
<div
style={{
maxHeight: isOpen ? '1000px' : '0px',
transition: 'max-height 1s ease-in-out',
}}
>
{children}
</div>
<button type="button" onClick={toggle}>
<span>Toggle content</span>
</button>
</>
);
};

props is undefined when passing from parent to component in next js

I have /pages/profile.js which calls the LovedOne element, passing values from props.
Debugging shows that these values are valid when passed
import React from "react";
import LovedOne from "../components/loved_one";
export const Profile = ({ loved_ones }) => {
const [session, loading] = useSession();
if (loading) return <div>loading...</div>;
if (!session) return <div>no session</div>;
return (
<Layout>
{session && (
<>
<img src={session.user.image} className="avatar" />
<h1>{session.user.name}</h1>
</>
)}
{loved_ones.map((loved_one, index) => (
<LovedOne
key={index}
firstname={loved_one.firstname}
surname={loved_one.surname}
email={loved_one.email}
/>
))}
<style jsx>{`
.avatar {
width: 220px;
border-radius: 10px;
}
`}</style>
</Layout>
);
};
However in /components/loved_one.js my props is undefined
import React, { useState, useRef } from "react";
export const LovedOne = ({ props }) => {
const [setActive, setActiveState] = useState("");
const [setHeight, setHeightState] = useState("0px");
const content = useRef();
function toggleAccordion() {
setActiveState(setActive === "" ? "active" : "");
setHeightState(
setActive === "active" ? "0px" : `${content.current.scrollHeight}px`
);
}
return (
<div>
<div className="row">
<button
className={`collection-item ${setActive}`}
onClick={toggleAccordion}
>
<i className="fas fa-plus teal-text"></i>
</button>
<div className="col s2">
{props.firstname} {props.surname}
</div>
<div className="col s2">{props.email}</div>
</div>
<div ref={content} style={{ maxHeight: `${setHeight}` }}>
<span>some stuff</span>
</div>
</div>
);
};
export default LovedOne;
I've tried passing single variables, and passing the entire loved_ones object. I get the same problem.
Any help much appreciated!
Have you tried passing props instead of {props} ?
lose brackets, try this way:
export const LovedOne = (props) => {

how to reload page after handle state?

My sidebar in the responsive mode not working correctly, i'm use #media for controller width of page, when is responsive i use position:absolute for sidebar button stay in up of content, i created a state for onclick is active change this position:relative but is not working, help please. The page in the mode normal funciton correctly, and mode responsive (Ctrl + shift + I) too but i click in the button the problemn happens.
Sidebar.js
export default class Menu extends Component {
constructor(props) {
super(props);
this.state = {
classStyle: "sidebar"
};
}
// handleSidebar(value) {
// this.setState = ({ classStyle : value });
// }
handleSidebar = (value) => {
this.setState = ({ classStyle: value });
}
render() {
return (
<div className={this.state.classStyle}>
<Navbar bg="light" variant="light" sticky="top" expand="lg">
<Navbar.Toggle aria-controls="navbarSupportedContent" onClick={() => handleSidebar("sidebarR")} />
<Navbar.Collapse id="navbarSupportedContent">
Index.css
#media (max-width: 600px)
{
.sidebar
{
position: absolute;
}
.sidebarR
{
position: relative;
}
}
Please try this one, it is working
Replace this function in your component
handleSidebar = () => {
console.log("clicked");
this.setState({ classStyle: "sidebarR" });
}
If you want toggle class then use below function,
handleSidebar = () => {
console.log("clicked");
const classStyle = this.state.classStyle == "sidebar" ? "sidebarR" : "sidebar";
this.setState({ classStyle: classStyle });
}
Running Component without Navbar Component,
import React,{Component} from 'react';
import './Menu.css';
export default class Menu extends Component {
state = {
classStyle: "sidebar"
};
handleSidebar = () => {
console.log("clicked");
this.setState({ classStyle: "sidebarR" });
}
render() {
return (
<div className={this.state.classStyle}>
<p onClick={() => this.handleSidebar()} >Menu</p>
</div>
)
}
}
When you call handleSidebar on onClick, you need to use this
Navbar.Toggle aria-controls="navbarSupportedContent" onClick={() => this.handleSidebar("sidebarR")} />

Save function not re-rendering while creating block

Save function while creating a block is not re-rendering in the front end. I made a component for the save which should re-render on state change but it is not. Edit function is working fine for admin.
Basically the setState function is not working for me.
I tried to enqueue the style but it also didn't worked for me.
My Save.js :
const { Component } = wp.element;
import './MyCss.css';
const { Icon } = wp.components;
import unsplash from './unsplash';
export class Save extends React.Component {
constructor(props) {
super(props)
this.state = {
images: [],
currentIndex: 0,
translateValue: 0,
translateValueSmall: 0
}
}
async componentDidMount(){
const response = await unsplash.get('/search/photos',{
params:{query: "cat"},
});
response.data.results.map(result=>{
this.setState(prevState => ({
images: [...prevState.images, result.urls.thumb]
}))
});
}
goToPrevSlide(){
console.log("previous slide");
if(this.state.currentIndex === 0)
return;
this.setState(prevState => ({
currentIndex: prevState.currentIndex - 1,
translateValue: prevState.translateValue + this.slideWidth(),
translateValueSmall: prevState.translateValueSmall + this.slideWidthSmall()/2
}))
}
goToNextSlide(){
if(this.state.currentIndex === this.state.images.length - 1) {
return this.setState({
currentIndex: 0,
translateValue: 0,
translateValueSmall: 0
})
}
this.setState(prevState => ({
currentIndex: prevState.currentIndex + 1,
translateValue: prevState.translateValue + -(this.slideWidth()),
translateValueSmall: prevState.translateValueSmall + -(this.slideWidthSmall())/2
}));
}
slideWidth(){
return document.querySelector('.slide').clientWidth
}
slideWidthSmall(){
return document.querySelector('.sliders').clientWidth
}
render() {
return (
<div className="box" onClick={()=>this.goToPrevSlide()}>
<div className="slider">
<div className="slider-wrapper"
style={{
transform: `translateX(${this.state.translateValue}px)`,
transition: 'transform ease-out 0.95s'
}}>
{
this.state.images.map((image, i) => (
<Slide key={i} image={image} />
))
}
</div>
</div>
<div className="sliders">
<LeftArrow
goToPrevSlide={()=>this.goToPrevSlide()}
/>
<RightArrow
goToNextSlide={()=>this.goToNextSlide()}
/>
<div className="chaitali"
style={{
transform: `translateX(${this.state.translateValueSmall}px)`,
transition: 'transform ease-out 0.95s'
}}>
{
this.state.images.map((image, i) => (
<Slide key={i} image={image} />
))
}
</div>
</div>
</div>
);
}
}
const Slide = ({ image }) => {
const styles = {
backgroundImage: `url(${image})`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: '50% 60%'
}
return <div className="slide" style={styles}></div>
}
const LeftArrow = (props) => {
return (
<div onClick={props.goToPrevSlide}>
<Icon className="back arrow" icon="arrow-left"/>
</div>
);
}
const RightArrow = (props) => {
return (
<div onClick={props.goToNextSlide}>
<Icon className="next arrow" icon="arrow-right"/>
</div>
);
}

Resources