React Transition Group Dynamically Change Slide Direction - reactjs

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

Related

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 to hide a component in react js after a few second

hi I design this alert component :
import React from 'react';
import { Alert } from 'antd';
export default function CustomAlert (props) {
return (
<React.Fragment>
{
props.visible ?
<Alert message={props.message} style={{textAlign: 'right', direction: 'rtl' , position: 'fixed', width:'100%', zIndex:'100'}} closable afterClose={props.handleClose}/>
:
null
}
</React.Fragment>
)
}
but I want to hide the alert after 5 second. how can I do this?
You can use setTimeout to auto close the Alert something like this. Find the working example here
export default function CustomAlert(props) {
const [visible, setVisible] = React.useState(props.visible);
React.useEffect(() => {
visible &&
setTimeout(() => {
setVisible(false);
}, props.closeAfter);
}, [props.closeAfter, visible, setVisible]);
return (
<React.Fragment>
{visible ? (
<Alert
message={props.message}
style={{
textAlign: "right",
direction: "rtl",
position: "fixed",
width: "100%",
zIndex: "100"
}}
closable
afterClose={props.handleClose}
/>
) : null}
</React.Fragment>
);
}
And use the component like this in other files,
<CustomAlert visible message="Test Message" closeAfter={5000} />
I solve my problem with this style:
.alert{
-moz-animation: cssAnimation 0s ease-in 15s forwards;
/* Firefox */
-webkit-animation: cssAnimation 0s ease-in 15s forwards;
/* Safari and Chrome */
-o-animation: cssAnimation 0s ease-in 15s forwards;
/* Opera */
animation: cssAnimation 0s ease-in 15s forwards;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
text-align: right ;
direction: rtl ;
position: fixed;
width: 100% ;
z-index: 100;
}
#keyframes cssAnimation {
to {
width:0;
height:0;
overflow:hidden;
z-index: -100;
}
}
#-webkit-keyframes cssAnimation {
to {
width:0;
height:0;
visibility:hidden;
z-index: -100;
}
}
I posted an answer with css styling but here is a better answer:
import React, {useEffect} from 'react';
import { Alert } from 'antd';
export default function CustomAlert(props) {
useEffect(() => {
setInterval(() => {
props.handleClose();
},5000 )
}, [])
return (
<React.Fragment>
{props.visible ? (
<Alert
message={props.message}
style={{
textAlign: "right",
direction: "rtl",
position: "fixed",
width: "100%",
zIndex: "100"
}}
closable
afterClose={props.handleClose}
/>
) : null}
</React.Fragment>
);
}

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;

CSSTransition from react-transition-group not applying classes

I'm trying to integrate CSSTransition to my Gatsby site, but it is not applying any of the classes. I'm utilizing CSS modules, and I've got a <div> that serves as the parent that fades in and out, essentially applying the fade effect to this and covering the content while it changes. It's got the class fadEffect. Here is my app-layout component, and the SASS.
AppLayout.tsx
import React, { ReactNode, useState } from 'react';
import { ApiContext } from 'contexts/ApiContext';
import { graphql, StaticQuery } from 'gatsby';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import { Devtools } from '../devtools/Devtools';
import { Footer } from '../footer/Footer';
import { Header } from '../header/Header';
import s from './AppLayout.scss';
interface AppLayoutProps {
children: ReactNode;
location: string;
}
const isDev = process.env.NODE_ENV === 'development';
// tslint:disable no-default-export
export default ({ children, location }: AppLayoutProps) => {
const [fadeEffectVisible, setFadeEffectVisible] = useState(false);
const handleFadeEffectEntered = () => {
setTimeout(() => {
setFadeEffectVisible(false);
}, 50);
};
return (
<StaticQuery
query={`${NavQuery}`}
render={(data) => (
<>
<ApiContext>
<Header navigationContent={data.prismic.allNavigations.edges[0].node} />
<CSSTransition
in={fadeEffectVisible}
timeout={150}
classNames={{
enter: s.fadeEffectEnter,
enterActive: s.fadeEffectEnterActive,
enterDone: s.fadeEffectEnterDone,
exit: s.fadeEffectExit,
exitActive: s.fadeEffectExitActive,
}}
onEntered={handleFadeEffectEntered}
>
<div className={s.fadeEffect} aria-hidden="true" />
</CSSTransition>
<TransitionGroup component={null}>
<CSSTransition
key={location}
timeout={150}
classNames={{
enter: s.pageEnter,
}}
>
<div className={s.layout}>
{children}
<Footer navigationItems={data.prismic.allNavigations.edges[0].node} />
{isDev && <Devtools />}
</div>
</CSSTransition>
</TransitionGroup>
</ApiContext>
</>
)}
/>
);
};
const NavQuery = graphql`
query NavQuery {
prismic {
allNavigations {
edges {
node {
...NotificationBar
...NavigationItems
...FooterNavigationItems
}
}
}
}
}
`;
AppLayout.scss
#import '~styles/config';
:global {
#import '~styles/base';
}
.layout {
display: block;
min-height: 100vh;
}
.pageEnter {
display: none;
}
.fadeEffect {
display: none;
position: fixed;
z-index: 9;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #fff;
transition: opacity 0.15s linear;
&Enter {
display: block;
opacity: 0;
}
&Active,
&Done,
&Exit {
display: block;
opacity: 1;
}
&ExitActive {
opacity: 0;
}
}
I'm happy to provide more details/code if this isn't enough. I'm newish to React and Gatsby, so I'm still learning the lingo. Thanks in advance.
I don't see part of your code where you are updating fadeEffectVisible to true for first CSSTransition and I don't see in property at all on second CSSTransition and I would bet that is your issue. Please take a look at this example from React Transition Group for understanding usage of properties.
App.js
function App() {
const [inProp, setInProp] = useState(false);
return (
<div>
<CSSTransition in={inProp} timeout={200} classNames="my-node">
<div>
{"I'll receive my-node-* classes"}
</div>
</CSSTransition>
<button type="button" onClick={() => setInProp(true)}>
Click to Enter
</button>
</div>
);
}
Style.css
.my-node-enter {
opacity: 0;
}
.my-node-enter-active {
opacity: 1;
transition: opacity 200ms;
}
.my-node-exit {
opacity: 1;
}
.my-node-exit-active {
opacity: 0;
transition: opacity 200ms;
}
When the in prop is set to true, the child component will first receive the class example-enter, then the example-enter-active will be added in the next tick.

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

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

Resources