Transition css animation works only once - css

I have a problem with css animation in react app triggering by useState. Animation between width changes works only once on one component. (I have array of tags on upper level)
...
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
console.log(isMounted);
setIsMounted(true);
// setIsMounted(false);
return () => {
console.log('There is');
setIsMounted(false);
}
}, []);
...
<div
id={item.label}
onKeyDown={onKeyDown}
className={isMounted ? 'i-tag' : 'no-tag' }
> {`some text`}
</div>
and scss here
.i-tag {
width: 100px;
position: relative;
max-width: max-content;
transition: width 1s ease-in;
}
.no-tag {
#extend .i-tag;
width: 0;
}

You can use TransitionGroup with CSSTransition for animating array of components.
Actually there are 2 ways to achieve animation of array of components in React,
By your own code which is bit trickier to do it perfectly
use some packages like 'react-transition-group' which is much easier to integrate
If you would like to use the package 'react-transition-group', here is the codesandbox on how to use it
<TransitionGroup component="ul">
{todos.map((todo) => (
<CSSTransition key={todo} timeout={700} classNames="item">
<li>
{todo}{" "}
<button
onClick={() => {
setTodos(todos.filter((t) => t !== todo));
}}
>
Delete
</button>
</li>
</CSSTransition>
))}
</TransitionGroup>
Courtesy: https://typeofnan.dev/how-to-animate-items-out-of-an-array-in-react/

Related

How to make dropdown animation?

I am implementing drop-down list using styled-component in react. In the process, I have two questions.
First, when dropDownVisible changes from true to false, why doesn't the animation effect apply and it disappears immediately? How can I improve the animation effect? Like when this list goes down, I want to make it gradually when it goes up.
Second, when StyledDropdown is dropped down, I want it to drop down behind the StyledHead, so I set the z-index property like that. I want the StyledHead to be always on top, so I'm curious why the StyledHead is hidden as the StyledDropdown drops down, even though I gave the z-index property bigger.
The source code is roughly structured like this:
// AApage.jsx
import { useEffect, useState, useRef } from 'react';
import { MdArrowDropDown, MdArrowDropUp } from 'react-icons/md';
import styled, { keyframes } from 'styled-components';
const dropAnimation = keyframes`
0% {
transform : translateY(-300px);
display : none;
}
100% {
transform : translateY(0);
}
`;
const StyledHead = styled.div`
width: 100px;
height: 100px;
background-color: red;
z-index: 11;
`;
const StyledDropdown = styled.div`
width: 100px;
height: 300px;
background-color: #d9d9d9;
border-radius: 0px 0px 10px 10px;
z-index: 3;
animation: ${dropAnimation} 1s alternate;
`;
const AApage = () => {
const [dropDownVisible, setDropDownVisible] = useState<boolean>(false);
const toggleDropDownVisible = () => {
setDropDownVisible((prev) => !prev);
};
return (
<>
<StyledHead>
<div>Dropdown</div>
<span>{`${dropDownVisible}`}</span>
{dropDownVisible ? (
<MdArrowDropUp
onClick={() => {
toggleDropDownVisible();
}}
></MdArrowDropUp>
) : (
<MdArrowDropDown
onClick={() => {
toggleDropDownVisible();
}}
></MdArrowDropDown>
)}
</StyledHead>
{dropDownVisible ? (
<StyledDropdown>
<div>temp data</div>
<div>temp data</div>
<div>temp data</div>
</StyledDropdown>
) : (
<></>
)}
</>
);
};
export default AApage;

Change state in function component react

I am new to React and being held back by a seemingly simple task.
I've got a Header component nested within which is a HamburgerButton component. Clicking the latter should make a sidenav appear but for now I would like the icon to change from the 'hamburger' to the big 'X'.
Here is my parent component:
import { MyMoviesLogo } from 'components/Icons';
import HamburgerButton from 'components/HamburgerButton/HamburgerButton';
import styles from './Header.module.css';
const Header = (): JSX.Element => {
const [isActive, setIsActive] = useState(false);
return (
<header className={styles.header}>
<MyMoviesLogo className={styles.headerIcon} />
<HamburgerButton
isActive={false}
/>
</header>
);
};
export default Header;
And here is the HamburgerButton
import styles from './HamburgerButton.module.css';
type HamburgerButtonProps = {
isActive: boolean;
onClick?: () => void;
};
const addMultipleClassNames = (classNames: string[]): string => classNames.join(' ');
const HamburgerButton = ({ isActive, onClick }: HamburgerButtonProps): JSX.Element => {
return (
<div className={isActive ? addMultipleClassNames([styles.hamburger, styles.active]) : styles.hamburger} onClick={onClick}>
<div className={styles.bar}></div>
<div className={styles.bar}></div>
<div className={styles.bar}></div>
</div>
);
}
export default HamburgerButton;
Here's my HamburgerButton.module.css file:
.hamburger {
cursor: pointer;
display: block;
width: 25px;
}
.bar {
background-color: var(--hamburger-button-global);
display: block;
height: 3px;
margin: 5px auto;
transition: all 0.3s ease-in-out;
width: 25px;
}
.hamburger.active .bar:nth-child(2) {
opacity: 0;
}
.hamburger.active .bar:nth-child(1) {
transform: translateY(8px) rotate(45deg);
}
.hamburger.active .bar:nth-child(3) {
transform: translateY(-8px) rotate(-45deg);
}
Manually changing the isActive prop to false verifies that the styling is applied as required.
My question is, how could I make it so when I click the icon its state gets toggled? I am familiar with React hooks like useState but can't quite put something together.
Any help would be greatly appreciated.
Thank you.
P.S.: It's probably obvious but I am using TypeScript.
You should use your onClick prop from your <HamburgerButton /> to change the parent state.
<HamburgerButton isActive={isActive} onClick={() => { setIsActive(oldState => !oldState) } />

How to give padding to items from parent in react

I'm using css-module to style react , i have a css file like this
.button-style {
flex-direction: row;
padding: 10px;
}
And an simple react file like this
import React from "react"
import { useEffect, useState, useCallback, useMemo } from "react"
import style from "./styleHook.module.css"
export default function TestEffect() {
const [count, setCount] = useState(1)
const [color, setColor] = useState("black")
const increaseCount = () => setCount(count + 1)
const double = (count: number) => count * 2
const changeColor = (color: string) => {
console.log("uh oh,this will re-render")
color === "black" ? setColor("blue") : setColor("black")
}
useEffect(() => {
console.log("hey count just change and i show up")
}, [count])
return (
<div>
<h1>Value of double count : {count}</h1>
<h2 style={{ color: color }}>This text have {color} color!</h2>
<div className={style['button-style']}>
<button
onClick={() => {
setCount(count + 1)
}}
>
Change value
</button>
<button
onClick={() => {
changeColor(color)
}}
>
Change color
</button>
<button onClick={increaseCount}>Increase count by 1</button>
</div>
</div>
)
}
Now i want to give some space between button, but don't know how but it give padding for parent instead of child, come from react-native and i feel like it not work like react-native
Please help, here is the codesandbox demo
Demo
add margin to buttons
button {
margin: 0 5px;
}
You can put these buttons into a div and apply grid property on that div.
Like this
<div style={{display:'grid', gridTemplateColumns:'repeat( auto-fit,minmax(250px, 1fr)', gap:'10px'}}>
<button1>1</button1>
<button2>2</button1>
<button3>3</button1>
</div>
Now they are responsive as well.

ReactTransitionGroup animation not playing on state change

I am trying to create an auto image slider with React and ReactTransitionGroup but I can't get animations to work. The image will change, but the transition effect won't play. I'm assuming it's the CSS because the class names do get added/removed.
Component
const ImgSlideshow = (props) => {
const images = [
'https://images.unsplash.com/photo-1593642634315-48f5414c3ad9?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80',
'https://images.unsplash.com/photo-1593642702821-c8da6771f0c6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1189&q=80',
'https://images.unsplash.com/photo-1593642532781-03e79bf5bec2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=634&q=80',
'https://images.unsplash.com/photo-1593642634443-44adaa06623a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=925&q=80',
'https://images.unsplash.com/photo-1593642634524-b40b5baae6bb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1189&q=80'
]
const [slide, setSlide] = useState(0);
useEffect(() => {
const slideshowTimer = setInterval(() => {
((slide + 1) > images.length - 1) ? setSlide(0) : setSlide(slide + 1)
}, 3000)
return () => clearInterval(slideshowTimer)
})
return (
<div className={styles.slideshow}>
<SwitchTransition>
<CSSTransition key={slide} timeout={200} classNames='slideshowImg'>
<img src={images[slide]} className="slideshowImg"></img>
</CSSTransition>
</SwitchTransition>
{props.children}
</div>
)
}
CSS
.slideshowImg-enter {
opacity: 0;
transform: translateX(-100%);
}
.slideshowImg-enter-active {
opacity: 1;
transform: translateX(0%);
}
.slideshowImg-exit {
opacity: 1;
transform: translateX(0%);
}
.slideshowImg-exit-active {
opacity: 0;
transform: translateX(100%);
}
.slideshowImg-enter-active,
.slideshowImg-exit-active {
transition: opacity 500ms, transform 500ms;
}
There are a couple of issues that I see.
You don't need to specify the className from the classNames prop on the child element that is being transitioned. If you have styles attached to this class then I would recommend you change it to something else to avoid confusion, but I don't think that is affecting your transitions.
You are setting transitions to 500ms but only allowing the transition group 200ms to complete the transition.
It also looks like you are just changing the image source instead of using a new element each time.
I have a working code sandbox here
I think you can achieve the desired result with this refactor:
const ImgSlideshow = (props) => {
const images = [
"https://images.unsplash.com/photo-1593642634315-48f5414c3ad9?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80",
"https://images.unsplash.com/photo-1593642702821-c8da6771f0c6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1189&q=80",
"https://images.unsplash.com/photo-1593642532781-03e79bf5bec2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=634&q=80",
"https://images.unsplash.com/photo-1593642634443-44adaa06623a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=925&q=80",
"https://images.unsplash.com/photo-1593642634524-b40b5baae6bb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1189&q=80"
];
const [slide, setSlide] = useState(0);
const imageElements = images.map((img, i) => {
return <img src={img} key={i} alt={`slideshow ${i+1}`} />;
});
useEffect(() => {
const slideshowTimer = setInterval(() => {
slide + 1 > images.length - 1 ? setSlide(0) : setSlide(slide + 1);
}, 3000);
return () => clearInterval(slideshowTimer);
});
return (
<div>
<SwitchTransition>
<CSSTransition key={slide} timeout={500} classNames="slideshowImg">
{imageElements[slide]}
</CSSTransition>
</SwitchTransition>
{props.children}
</div>
);
};
My snippet uses the map function to create an array of img elements, each with a src that is correspondent to the index of the images array. This way you will have multiple elements to transition in between. Right now you only have one image element, and the react-transition-group can't put an animation on the changing of the image src (afaik).

Hide nav button in react-material-ui-carousel

I've just implemented the react material ui carousel, and it was pretty straightforward, the only thing i didn't catch, is how to hide buttons and show them only on over.
I noticed the props navButtonsAlwaysVisible and set it to false but it isn't enough.
Should i implement my own logic for that, or maybe I'm just missing something?
here's the component code:
import styles from '../../styles/Testimonial.module.scss'
import Image from 'next/image'
import Carousel from 'react-material-ui-carousel'
const Testimonial = _ => {
const items = [
{
imageUrl: "/png/image0.webp",
feedback: "feedback0",
name: "name0",
location: "location0"
},
{
imageUrl: "/png/image1.jpeg",
feedback: "feedback1",
name: "name1",
location: "location1"
}
]
return (
<div id="customers" className={`section ${styles.testimonial}`}>
<h2 className={`title ${styles.title}`}>Clientes Felizes</h2>
<span className={"separator"}> </span>
<Carousel
className={styles.carousel}
autoPlay={true}
stopAutoPlayOnHover={true}
interval={5000}
animation={"slide"}
swipe={true}
navButtonsAlwaysVisible={false}
navButtonsProps={{
style: {
backgroundColor: "#8f34eb",
opacity: 0.4
}
}}
>
{
items.map( (item, i) => <Item key={i} item={item} /> )
}
</Carousel>
</div>
)
}
function Item(props)
{
return (
<article className={styles.testimonial__card}>
<div className={styles.testimonial__photo_container}>
<Image
className={styles.testimonial__photo}
src={props.item.imageUrl}
alt="Testimonial"
width={312}
height={300}
/>
</div>
<p className={styles.testimonial__copy}>{props.item.feedback}</p>
<span className={styles.testimonial__name}>{props.item.name}</span>
<span className={styles.testimonial__city}>{props.item.location}</span>
</article>
)
}
export default Testimonial;
there's a prop called navButtonsAlwaysInvisible
navButtonsAlwaysInvisible={true}
You can try using Custom CSS for your purpose. Based on the current rendered markup,
.jss6 {
opacity: 0;
transition: all ease 1000ms; /* So that it does not disappear quickly */
}
You can define the hover for the parent so that it displays only when the parent container is hovered on:
.jss1.Testimonial_carousel__3rny3:hover .jss6 {
opacity: 1;
}
This is how it works now:

Resources