React - fade in on add, fade out on remove item - reactjs

Hello fellow react developers!
I am trying to make a list of items, with two basic operations: add item and remove item.
What I want to do is whenever I add an item, I want it to have a nice fade in effect (newly added item fades in), and when I remove it, I want it to fade-out (the removed item fades out).
What would be the simplest or most straightforward way of achieving this effect on the given implementation (or if the implementation needs to be adjusted to do it, that works as well :))?
function App() {
const [items, setItems] = useState([]);
return (
<div>
<button
className='button-add'
onClick={() => setItems([...items, {
id: new Date().getUTCMilliseconds().toString()}])}
>
Add item
</button>
{items.map(item => (
<div className='item'>
<span className='item-name'>{item.id}</span>
<button className='button-remove' onClick={() => setItems(items.filter((itemInner) => itemInner.id !== item.id))}>Remove item</button>
</div>
))}
</div>
)
}
Working example (add/remove) on codepen

Keyframes are what you are looking for.
I came up with this solution, you might need to tweak it to fit your needs:
Css file:
.item {
-webkit-animation: fadein .3s linear forwards;
animation: fadein .3s linear forwards;
padding: 10px;
}
.item-fadeout{
display: flex;
align-items: center;
padding: 10px;
-webkit-animation: fadeout .3s linear forwards;
animation: fadeout .3s linear forwards;
}
#-webkit-keyframes fadein {
0% { opacity: 0; }
100% { opacity: 1; }
}
#keyframes fadein {
0% { opacity: 0; }
100% { opacity: 1; }
}
#-webkit-keyframes fadeout {
0% { opacity: 1; }
100% { opacity: 0; }
}
#keyframes fadeout {
0% { opacity: 1; }
100% { opacity: 0; }
}
JS:
const { useState } = React;
function Item(props) {
const [isFadingOut, setIsFadingOut] = useState(false);
const fadeOut = (cb) => {
setIsFadingOut(true);
cb();
};
const handleRemoveItem = () => {
props.removeItem();
setIsFadingOut(false);
};
return (
<div className={isFadingOut ? 'item-fadeout' : 'item'}>
<span className='item-name'>{props.item.id}</span>
<button
className='button-remove'
onClick={() => fadeOut(setTimeout(() => handleRemoveItem(), 300))}
>
Remove item
</button>
</div>
);
}
function App() {
const [items, setItems] = useState([]);
return (
<div>
<button
className='button-add'
onClick={() =>
setItems([
...items,
{
id: new Date().getUTCMilliseconds().toString(),
},
])
}
>
Add item
</button>
{items.map((item) => (
<Item
item={item}
removeItem={() =>
setItems(items.filter((itemInner) => itemInner.id !== item.id))
}
/>
))}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('app'));
Working sample: https://codepen.io/luismendes535/pen/YzyJXdR

Related

How do you apply a class everytime you click the button in React?

I have a slide in animation that I want to kick in everytime one of the dots in the carousel is clicked. However with my current code the state is always true and if I set it to a "toggling" state, it will be "true,false" meaning the class will only be applied every one click of the dots (when is true).
I want the slide in effect to apply on every dot that is clicked. Code here:
JSX
const [onClick, setClick] = useState(false);
const handleDotClick = () => {
setClick(onClick => !onClick);
};
<div className={classnames(styles.grid, { [styles.slideIn]: onClick })}>
{data[0].map(({ image }) => {
return (
<div>
<Image src={`image.jpg`}/>
</div>
);
})}
</div>
<div className={styles.dots}>
{Array.apply(null, { length: 10 }).map((e, i) => (
<span onClick={handleDotClick}></span>
))}
</div>
CSS
.slideIn {
-webkit-animation: slideIn 0.5s forwards;
-moz-animation: slideIn 0.5s forwards;
animation: slideIn 0.5s forwards;
}
#-webkit-keyframes slideIn {
0% {
transform: translateX(900px);
}
100% {
transform: translateX(0);
}
}
#-moz-keyframes slideIn {
0% {
transform: translateX(900px);
}
100% {
transform: translateX(0);
}
}
#keyframes slideIn {
0% {
transform: translateX(900px);
}
100% {
transform: translateX(0);
}
}
Any idea on how to apply the slideIn class everytime the dot is clicked?
As per my understanding here is an example using useRef to apply the slideIn animation on each time a dot is clicked.
const ref = useRef();
const handleDotClick = () => {
if (ref.current) {
requestAnimationFrame(() => {
ref.current.classList.remove("slideIn");
});
requestAnimationFrame(() => {
ref.current.classList.add("slideIn");
});
}
};
<div ref={ref} className={classnames(styles.grid, { [styles.slideIn]: onClick })}>
{data[0].map(({ image }) => {
return (
<div>
<Image src={`image.jpg`} />
</div>
);
})}
</div>
<div className={styles.dots}>
{Array.apply(null, { length: 10 }).map((e, i) => (
<span onClick={handleDotClick}></span>
))}
</div>

React Transition Group Dynamically Change Slide Direction

I'm creating a mobile navigation menu and using CSSTransition component from React Transition Group to handle animating in the different levels of my navigation.
I am able to successfully animate in different levels but only in one direction. For instance, the content will enter in from the right and exit to the left. The part that is confusing to me is when i need to change the direction that the content animates in or out.
For my example lets say I have 3 slides
Initially slide #2 would enter in from the right If we go to slide #3 it would exit left.
If we are on slide #3 and want to go back to slide #2 then I want slide #2 to enter in from the right.
import { useState } from 'react';
import { CSSTransition } from 'react-transition-group';
import './App.css';
const App = () => {
const [page, setPage] = useState(1);
return (
<>
Page: {page}
<nav>
<button onClick={() => page !== 1 && setPage(page - 1)}>Prev</button>
<button onClick={() => page >= 1 && page <= 2 && setPage(page + 1)}>
Next
</button>
</nav>
<div className='content'>
<CSSTransition
in={page === 1}
classNames='slide'
timeout={500}
unmountOnExit
>
<SlideOne />
</CSSTransition>
<CSSTransition
in={page === 2}
classNames='slide'
timeout={500}
unmountOnExit
>
<SlideTwo />
</CSSTransition>
<CSSTransition
in={page === 3}
classNames='slide'
timeout={500}
unmountOnExit
>
<SlideThree />
</CSSTransition>
</div>
</>
);
};
const SlideOne = () => {
return <h3>Hello From Slide One</h3>;
};
const SlideTwo = () => {
return <h3>Hello From Slide Two</h3>;
};
const SlideThree = () => {
return <h3>Hello From Slide Three</h3>;
};
export default App;
.content {
width: 200px;
height: 100px;
overflow: hidden;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
padding: 1rem;
position: relative;
}
h3 {
position: absolute;
}
.slide-enter {
opacity: 0;
transform: translateX(100%);
}
.slide-enter-active {
opacity: 1;
transform: translateX(0%);
transition: all 0.5s;
}
.slide-exit {
opacity: 1;
transform: translateX(0%);
}
.slide-exit-active {
opacity: 0;
transform: translateX(-100%);
transition: all 0.5s;
}
import { useState } from "react";
import { CSSTransition, TransitionGroup } from "react-transition-group";
import "./App.css";
const App = () => {
const [page, setPage] = useState(1);
const [direction, setDirection] = useState("left");
return (
<>
Page: {page}
<nav>
<button
onClick={() => {
page !== 1 && setPage(page - 1);
direction !== "right" && setDirection("right");
}}
>
{" "}
Prev
</button>
<button
onClick={() => {
page >= 1 && page <= 2 && setPage(page + 1);
direction !== "left" && setDirection("left");
}}
>
Next
</button>
</nav>
<div className="content">
{/* <TransitionGroup> */}
<CSSTransition
in={page === 1}
classNames={`slide-${direction}`}
timeout={500}
unmountOnExit
>
<SlideOne />
</CSSTransition>
<CSSTransition
in={page === 2}
classNames={`slide-${direction}`}
timeout={500}
unmountOnExit
>
<SlideTwo />
</CSSTransition>
<CSSTransition
in={page === 3}
classNames={`slide-${direction}`}
timeout={500}
unmountOnExit
>
<SlideThree />
</CSSTransition>
{/* </TransitionGroup> */}
</div>
</>
);
};
const SlideOne = () => {
return <h3>Hello From Slide One</h3>;
};
const SlideTwo = () => {
return <h3>Hello From Slide Two</h3>;
};
const SlideThree = () => {
return <h3>Hello From Slide Three</h3>;
};
export default App;
.content {
width: 200px;
height: 100px;
overflow: hidden;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
padding: 1rem;
position: relative;
}
h3 {
position: absolute;
}
.slide-left-enter {
opacity: 0;
transform: translateX(100%);
}
.slide-left-enter-active {
opacity: 1;
transform: translateX(0%);
transition: all 2000ms;
}
.slide-left-exit {
opacity: 1;
transform: translateX(0%);
}
.slide-left-exit-active {
opacity: 0;
transform: translateX(-100%);
transition: all 2000ms;
}
.slide-right-enter {
opacity: 0;
transform: translateX(-100%);
}
.slide-right-enter-active {
opacity: 1;
transform: translateX(0%);
transition: all 2000ms;
}
.slide-right-exit {
opacity: 1;
transform: translateX(0%);
}
.slide-right-exit-active {
opacity: 0;
transform: translateX(100%);
transition: all 2000ms;
}
the trick is to reverse the translation on every key button

React collapse not working after navbar set to fixed

In my react app, using React-Bootstrap, I set the navbar to fixed after srcolling, but after that I'm unable to use the toggler button, that was working before scrolling :
const [sticky, setSticky] = useState('');
React.useEffect(() => {
window.addEventListener('scroll', stickNavbar);
return () => window.removeEventListener('scroll', stickNavbar);
}, []);
const stickNavbar = () => {
if (window !== undefined) {
let windowHeight = window.scrollY;
// window height changed for the demo
windowHeight > 150 ? setSticky('top') : setSticky('');
}
};
return (
<Navbar bg="light" fixed={stickyClass} className="menu">
<Container fluid >
</Container>
</Navbar>
<Collapse in={open} timeout={200}>
<Container fluid className="dropdown-container">
<Row className="dropdown">
</Row>
</Container>
</Collapse>
)
CSS
.menu {
max-width: 100%;
height: 7rem;
border-bottom: 2px solid rgb(228, 228, 228);
}
.dropdown-container {
position: absolute;
}
.dropdown {
background-color: #f8f9fa;
border-bottom: 2px solid rgb(228, 228, 228);
position: relative;
}
Solution: I created for the useState, useEffect and stickNavbar corresponding items:
const [scroll, setScroll] = useState(0);
React.useEffect(() => {
window.addEventListener('scroll', stickNavbar);
return () => window.removeEventListener('scroll', stickNavbar);
}, []);
const scrollPos = () => {
if (window !== undefined) {
let posHeight = window.scrollY;
setScroll(posHeight)
}
};
than I used them to follow the change in the scroll Y coordinates to set the distance of the position + rem in pixels of the height of the navbar:
<Container fluid className="dropdown-container" style=
{{top:`${112+scroll}`+"px"}}>

How do I make the slideshow buttons scroll through the images?

I have been working on a project and while trying to create a slideshow I found somewhere, wanted to edit it so that instead of using the setTimeout to scroll through the images or the circles, I want to be able to use the buttons to go left and right but just cant seem to figure it out.
Here is the link to what it looks like:
https://codesandbox.io/s/throbbing-bash-ir9g0?fontsize=14&hidenavigation=1&theme=dark
Anyone able to point me in the right direction?
In order to achieve what you are looking for, we just need to add click handlers for left and right buttons.
Below are the respective functions in order to display the next/previous slide based on the current index.
//Handler to update the current index on click of right button
const onNextClick = () => {
if(index !== colors.length - 1) {
setIndex(idx => idx + 1);
}
}
//Handler to update the current index on click of left button
const onPreviousClick = () => {
if(index !== 0) {
setIndex(idx => idx - 1);
}
}
<button className="left" onClick={onPreviousClick}>‹</button>
<button className="right" onClick={onNextClick}>›</button>
Adding these functions to slideshow.js & updating the buttons as shown will help you in achieving the expected functionality.
Here is the demo
const colors = ["#0088fE", "#00C49F", "#FFBB28"];
const delay = 5000;
function Slideshow() {
const [index, setIndex] = React.useState(0);
const timeoutRef = React.useRef(null);
function resetTimeout() {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
}
React.useEffect(() => {
resetTimeout();
timeoutRef.current = setTimeout(
() =>
setIndex((prevIndex) =>
prevIndex === colors.length - 1 ? 0 : prevIndex + 1
),
delay
);
return () => {
resetTimeout();
};
}, [index]);
//Handler to update the current index on click of right button
const onNextClick = () => {
if(index !== colors.length - 1) {
setIndex(idx => idx + 1);
}
}
//Handler to update the current index on click of left button
const onPreviousClick = () => {
if(index !== 0) {
setIndex(idx => idx - 1);
}
}
return (
<div className="slideshow">
<div
className="slideshow-slider"
style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}
>
{colors.map((backgroundColor, index) => (
<div className="slide" key={index} style={{ backgroundColor }} />
))}
</div>
<button className="left" onClick={onPreviousClick}>‹</button>
<button className="right" onClick={onNextClick}>›</button>
<div className="slideshow-tabs">
{colors.map((_, idx) => (
<div
key={idx}
className={`slideshow-tab${index === idx ? " active" : ""}`}
onClick={() => {
setIndex(idx);
}}
></div>
))}
</div>
</div>
);
}
ReactDOM.render(<Slideshow />, document.getElementById("react"));
.slideshow {
margin: 0 auto;
overflow: hidden;
width: 30%;
position: relative;
}
.slideshow-slider {
white-space: nowrap;transition: ease 1000ms;
}
.slide {
display: inline-block;
height: 240px;
width: 100%;
}
button {
width: 25px;
height: 240px;
position: absolute;
cursor: pointer;
}
.left {
left: 0;
top: 0;
}
.right {
right: 0;
top: 0;
}
.slideshow-tabs {
text-align: center;
position: relative;
bottom: 50px;
}
.slideshow-tab {
display: inline-block;
height: 20px;
width: 20px;
border-radius: 50%;
cursor: pointer;
margin: 15px 7px 0px;
background-color: #c4c4c4;
}
.active {
background-color: #6a0dad;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>
-- Update --
As per you requirement, if you wish to go back to the beginning at the end of all the slides, you can make the changes to onNextClick like below
const onNextClick = () => {
setIndex(idx => idx !== colors.length - 1? idx + 1: 0);
}
you can do that by handling the onClick event
here's the link of the code you could check this demo
import React from "react";
import "./slideshow.scss";
const colors = ["#0088fE", "#00C49F", "#FFBB28"];
function Slideshow() {
const [index, setIndex] = React.useState(0);
const handleChange=()=>{
setIndex((prevIndex) =>
prevIndex === colors.length - 1 ? 0 : prevIndex + 1
)
}
React.useEffect(() => {
}, [index]);
return (
<div className="slideshow">
<div
className="slideshow-slider"
style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}
>
{colors.map((backgroundColor, index) => (
<div className="slide" key={index} style={{ backgroundColor }} />
))}
</div>
<button className="left" onClick={()=>handleChange} >‹</button>
<button className="right" onClick={()=>handleChange}>›</button>
<div className="slideshow-tabs">
{colors.map((_, idx) => (
<div
key={idx}
className={`slideshow-tab${index === idx ? " active" : ""}`}
onClick={() => {
setIndex(idx);
}}
></div>
))}
</div>
</div>
);
}
export default Slideshow;

React Hooks Drawer Menu not Showing CSS Transition

My Menu Drawer is working except for the css transitions. I think whta is happening is, when I change the value of menuOpen (which is a useState), the DOM rerenders and the transition never happens. How do I stop this? I think I need to use the useRef I have already, but not sure how?
My Page Component with a white div that will be the drawer:
import React, { useState, useEffect, useRef } from 'react';
import { Typography } from '#material-ui/core';
import './page.css';
function Page({ someProps }) {
const [ menuOpen, setMenuOpen ] = useState(false);
const menuRef = useRef();
const handleMenuClick = () => {
setMenuOpen(!menuOpen);
console.log('MENU CLICKED!!!!!!!!!!!!!!!!!!!!', menuOpen);
};
const handleClickOutside = (event) => {
console.log('CLICKED!!!!!!!!!!!!!!!!!!!!', event, menuRef.current);
if (menuRef.current && !menuRef.current.contains(event.target) && menuOpen === true) {
setMenuOpen(false);
}
};
useEffect(
() => {
document.addEventListener('click', handleClickOutside, false);
return () => {
document.removeEventListener('click', handleClickOutside, false);
};
},
[ menuOpen ]
);
return (
<Typography className="screen">
<div className="menuButton" onClick={handleMenuClick}>
MENU
</div>
{menuOpen && <div ref={menuRef} className={`menuContainer ${menuOpen === true ? 'isOpen' : ''}`} />}
</Typography>
);
}
export default Page;
My page.css:
.menuContainer {
position: fixed;
top: 0;
left: 0;
width: 250px;
height: 100vh;
background-color: white;
z-index: 1;
transition: margin 1s ease-in;
margin: 0 0 0 -250px;
}
.menuContainer.isOpen {
margin: 0 0 0 0px;
transition: margin 2s;
}

Resources