Problem with exit and enter animations using react-transition-group - javascript

I'm new to React and I'm looking for some help.
I'm trying to build a simple login page. The page gives users three options ('sign in with email', 'sign in with Google', or 'sign up') and displays a component based on the option chosen.
I wanted to add a simple cross-fade as these components enter and exit the DOM so I turned to react-transition-group.
The problem, however, is my components don't exit and enter properly. The old component appears to disappear instantly rather than fade out, two copies of the 'new' component are stacked on top of each other. One fades out and the other fades in. The old and new don't elegantly cross-fade into each other as I would like.
Example:
Initial login page
On click, two copies of the 'new' component appear, one fades in and the other fades out
transition completes and container now holds the new component as desired
I've looked over the docs for react-transition-group but I can't quite figure out what I'm doing wrong.
Here's my main component:
class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
display: "loginInitial"
}
};
handleEmailLogin = () => {
this.setState(() => ({
display: "emailLogin"
}))
};
handleGoogleLogin = () => {
console.log("Logging in with google!")
this.props.startGoogleLogin()
};
handleEmailSignUp= () => {
this.setState(() => ({
display: "emailSignUp"
}))
};
handleBackToStart = () => {
this.setState(() => ({
display: "loginInitial",
}))
}
displayBox = () => {
if (this.state.display === "loginInitial") {
return <LoginPageInitial
handleEmailLogin={this.handleEmailLogin}
handleGoogleLogin={this.handleGoogleLogin}
handleEmailSignUp={this.handleEmailSignUp}
/>
} else if (this.state.display === "emailSignUp") {
return <LoginPageEmailSignUp
handleBackToStart={this.handleBackToStart}
/>
} else if (this.state.display === "emailLogin") {
return <LoginPageEmailLogin
handleBackToStart={this.handleBackToStart}
/>
}
}
render() {
return (
<div className="box-layout">
<div className="box-layout__box">
<h1 className="box-layout__title">My app</h1>
<p>Subtitle goes here blah blah blah blah.</p>
<TransitionGroup>
<CSSTransition
classNames="fade"
timeout={600}
key={this.state.display}
>
{this.displayBox}
</CSSTransition>
</TransitionGroup>
</div>
</div>
)
};
}
const mapDispatchToProps = (dispatch) => ({
startGoogleLogin: () => dispatch(startGoogleLogin())
})
export default connect(undefined, mapDispatchToProps)(LoginPage)
And here's the relevant CSS:
.box-layout {
align-items: center;
background: url('/images/bg.jpg');
background-size: cover;
display: flex;
justify-content: center;
height: 100vh;
width: 100vw;
}
.box-layout__box {
background: fade-out(white,.15);
border-radius: 3px;
text-align: center;
width: 30rem;
padding: $l-size $m-size
}
.box-layout__title {
margin: 0 0 $m-size 0;
line-height: 1;
}
// FADE
// appear
.fade-appear {
opacity: 0;
z-index: 1;
}
.fade-appear.fade-appear-active {
opacity: 1;
transition: opacity 600ms linear;
}
// enter
.fade-enter {
opacity: 0;
z-index: 1;
}
.fade-enter.fade-enter-active {
opacity: 1;
transition: opacity 600ms linear;
}
// exit
.fade-exit {
opacity: 1;
}
.fade-exit.fade-exit-active {
opacity: 0;
transition: opacity 600ms linear;
}
I would be extremely appreciative if anyone can offer some advice. I've done a lot of googling but I can't find a solution to my problem.
[EDIT: MOSTLY SOLVED - I managed to solve the bulk of my problem. It appears I needed to place my 'key' prop in my component rather than component. Two versions of the 'new' component no longer appear and the container doesn't stretch. It does appear, however, that the 'old' component still doesn't fade out as I expected. It looks a lot better than it did though. I welcome any further insight on what I was/am doing wrong or how I can improve my code.)

Related

React.js - How to check if the div is at the bottom of the screen (dynamically)

Is there any way to check if the div is at the bottom of the another div (acting as a parent, or container).
What I tried
So basically I made demo where there are child elements (items, setItems) in the div that can be added and deleted and also you can change the height of them by clicking on the divs (important here). Also there is another div, which is not in the items state, where I want to change the title of that item, if it is at the bottom of the his parent div (also items have the same parent as this div has).
Problem with my solution
I have tried something where I am looking at the getBoundingClientRect() of the parent container and this "blue" div, lets call it like that, and it will work fine, ONLY IF the items have the same height, but soon as a delete the one item and change the height of it by clicking on the div, it will not work. It will show that it is on the bottom of the screen (the title will be true) but in reality it is not.
My code
App.js - only for demo purposes
import "./styles.css";
import { useState, useEffect, useRef } from "react";
export default function App() {
const arrayItems = [
{
id: 1,
name: "test",
resized: false
},
{
id: 2,
name: "test1",
resized: false
},
{
id: 3,
name: "test2",
resized: false
}
];
const [items, setItems] = useState(arrayItems);
const [title, setTitle] = useState(false);
const parentRef = useRef(null);
const itemsRef = useRef(null);
useEffect(() => {
if (
parentRef?.current.getBoundingClientRect().bottom -
itemsRef?.current.getBoundingClientRect().height <=
itemsRef?.current.getBoundingClientRect().top
) {
setTitle(true);
} else {
setTitle(false);
}
}, [parentRef, items]);
const handleClick = () => {
const maxValue = Math.max(...items.map((item) => item.id)) + 1;
setItems((prev) => [
...prev,
{ id: maxValue, name: "testValue", resized: false }
]);
};
const handleDelete = () => {
setItems((prev) => prev.slice(0, prev.length - 1));
};
const handleResize = (item) => {
setItems((prev) =>
prev.map((itemOld) => {
if (itemOld.id === item.id) {
return itemOld.resized === true
? { ...itemOld, resized: false }
: { ...itemOld, resized: true };
} else {
return itemOld;
}
})
);
};
console.log(items);
return (
<div className="App">
<button onClick={handleClick}>Add new</button>
<button onClick={handleDelete}>Delete last</button>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div ref={parentRef} className="container">
{items?.map((item) => {
return (
<div
onClick={() => handleResize(item)}
style={{ height: item.resized ? "70px" : "20px" }}
key={item.id}
className="container-item"
>
<p>{item.name}</p>
</div>
);
})}
<div ref={itemsRef} id="title-div">
{title ? "At the bottom" : "Not at the bottom"}
</div>
</div>
</div>
);
}
styles.css
.App {
font-family: sans-serif;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 1rem;
}
* {
margin: 0;
padding: 0;
}
.container {
width: 600px;
height: 300px;
background-color: gray;
display: flex;
flex-direction: column;
}
.container-item {
width: 100%;
background-color: hotpink;
}
#title-div {
width: 100%;
padding: 1rem;
background-color: blue;
color: white;
}
What I want to make
As the title suggest I want to see if the div is at the bottom of the container/parent div. That is it, and other items in that parent div, cannot interfere with this div, in sense that adding, resizing, deleting those items, will not suddenly change the position of the div that I want to analyse (to see if it is at the bottom of the screen)
I have come up with my own solution and it works always. I just have to deduct the "top" from parentsRef and "top" from the itemsRef, and add to that the clientHeight of the itemsRef. This way it will always be at the bottom of the container, doesnt matter if I delete the items, resize them etc.
The code
useEffect(() => {
if (
parentRef?.current.clientHeight <=
itemsRef?.current.getBoundingClientRect().top -
parentRef?.current.getBoundingClientRect().top +
itemsRef?.current.clientHeight
) {
setTitle(true);
} else {
setTitle(false);
}
}, [parentRef, items, itemsRef]);

How to pass two state variables into the same React useState? - Array & Integer

Working on an e-commerce store project where I have a Slider component.
I am fetching my own JSON data from a localhost server & mapping over the array for each slide.
My goal is to find a way to use the same useState() to render the items onto the page & click a button to move to the next slide.
I have tried to do something like useState([], 0)
One for my array & another one to change index on button click however this did not work lol...
The array is of course for the data to be displayed however the tricky part for me is figuring out a way to move to the next page.
I am trying to use transform:translateX in my Wrapper styled component and attempting to pass props in so I can change the slide to the next slide and still render the data on the page.
How can I go about using state in this way described above based on my code?
*** Before reading the code snippets, my current code shows that I have tried creating two states, and passing the second state with my integer into my fetch request, no errors pop up but it doesn't work obviously as it doesn't have an array of items to index through.
In the code snippet I have included the code for the entire slider & also the information inside my data.json file.
import {useState, useEffect} from 'react';
import { ArrowLeftOutlined, ArrowRightOutlined } from "#material-ui/icons";
import styled from "styled-components";
const Container = styled.div`
width: 100%;
height: 95vh;
display: flex;
// background-color: #b3f0ff;
position: relative;
overflow: hidden;s
`;
const Arrow = styled.div`
width: 50px;
height: 50px;
background-color: #e6ffff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 0;
bottom: 0;
left: ${props => props.direction === "left" && "10px"};
right: ${props => props.direction === "right" && "10px"};
margin: auto;
cursor: pointer;
opacity: 0.5;
z-index: 2;
`;
const Wrapper = styled.div`
height: 100%;
display: flex;
transform: translateX({props => props.arrowIndx * -100}vw);
`
const Slide = styled.div`
width: 100vw;
height: 100vw;
display: flex;
align-items: center;
background-color: ${props => props.bg};
`
const ImgContainer = styled.div`
height: 100%;
flex:1;
`
const Image = styled.img`
padding-left: 30px;
align-items: left;
`
const InfoContainer = styled.div`
height: 80%;
flex:1;
padding: 30px;
`
const Title = styled.h1`
font-size: 50px
`
const Desc = styled.p`
margin: 50px 0px;
font-size: 20px;
font-weight: 500;
letter-spacing: 3px;
`
const Button = styled.button`
padding: 10px;
font-size: 20px;
background-color: transparent;
cursor: pointer;
`
const Slider = () => {
const [slideIndx, setSlideIndx] = useState([]);
const [arrowIndx, setArrowIndx] = useState(0);
const handleClick = (direction) => {
if(direction === "left"){
setArrowIndx(arrowIndx > 0 ? arrowIndx - 1 : 2)
} else{
setArrowIndx(arrowIndx < 2 ? arrowIndx + 1 : 0)
}
}
const fetchSliderItems = () => {
fetch('http://localhost:3000/sliderItems')
.then(resp => resp.json())
.then(data => {
console.log(data)
setArrowIndx(data)
setSlideIndx(data)
})
}
useEffect(() => {fetchSliderItems()}, [])
return (
<Container>
<Arrow direction="left" onClick={() => handleClick("left")}>
<ArrowLeftOutlined />
</Arrow>
<Wrapper arrowIndx={arrowIndx}>
{slideIndx.map((item) => (
<Slide bg={item.bg}>
<ImgContainer>
<Image src={item.img}/>
</ImgContainer>
<InfoContainer>
<Title>{item.title}</Title>
<Desc>{item.desc}</Desc>
<Button>SHOP NOW</Button>
</InfoContainer>
</Slide>
))}
</Wrapper>
<Arrow direction="right" onClick={() => handleClick("right")}>
<ArrowRightOutlined />
</Arrow>
</Container>
)
}
export default Slider
{
"sliderItems": [
{
"id": 1,
"img": "../images/model1.png",
"title": "SPRING CLEANING",
"desc": "DONT MISS OUR BEST COLLECTION YET! USE #FLATIRON10 TO RECEIVE 10% OFF YOUR FIRST ORDER",
"bg": "#b3ecff"
},
{
"id": 2,
"img": "../images/model2.png",
"title": "SHOW OFF HOW YOU DRESS",
"desc": "WITH OUR HUGE SELECTION OF CLOTHES WE FIT ALL YOUR STYLING NEEDS",
"bg": "#ccf2ff"
},
{
"id": 3,
"img": "../images/model3.png",
"title": "POPULAR DEALS",
"desc": "RECEIVE FREE SHIPPING ON ALL ORDERS OVER $50!",
"bg": "#fe6f9ff"
}
]
}
if you want to use 2 data at 1 use state you have 2 way to achieve that
store data as cell of array like below.
const [data,setData] = useState([1,2])
console.log(data[1])
set data as Object in useState
const [data,setData] = useState({data1: 1, data2: 2 })
but i suggest you to use second approach because it's easy to use.
one point you must have care about it in this approach is, if you want to update state with object-state you have to update state with deep copy and then react can re-render component
for example if you want to update data2 in object of state you have to dod this
const [data,setData] = useState({data1: 1, data2: 2 })
setData((prevState) => ({
...prevState,
data2: 'value',
}));
white this snippet you will get update data2 and react get re-render
in your case if you want save array and update it and if you want get re-render when pass new array you have to clone new array and pass it into useState and you can archive this with spread operator in JavaScript.
Example:
setData((prevState) => ({
...prevState,
data2: [...newArray],
}));
if you pass newArray instead of [...newArray] you just pass the reference of memory if array into state and if after setState you change newAereay at the rest of code your state will update to and you don't want this.

how to group multiple selectors of a CSS module in the className

So sometimes I would group 2 selectors in the className at the same time like <div className="slectorA selectorB" /> which would render .selectorA .selectorB { (...) } in the CSS file if I am not mistaken.
I was wondering how to do that if I were to use a CSS module. Define styles = './App.module.css' then how would I do the same call as before? Here is an example of what I am trying to do in my code.
App.js
import styles from "./App.module.css";
const manageBackgroundImage = (temperature) => {
if (temperature > 16) return "{styles.weather_box.warm}";
else return "{styles.weather_box.cold}";
//(...more else ifs here...)
};
export default function App() {
const temperature = 17;
return (
<div>
<div className={manageBackgroundImage(temperature)}></div>
</div>
);
}
App.module.css
.weather_box {
display: grid;
width: 40%;
height: inherit;
}
.weather_box.cold {
background-image: url('../../assets/cold.png');
transition: 0.6s ease-out;
}
.weather_box.warm {
background-image: url('../../assets/warm.png');
transition: 0.6s ease-out;
}
App.js
import styles from "./App.module.css";
const manageBackgroundImage = (temperature) => {
if (temperature > 16) return `${styles.weather_box} ${styles.warm}`;
else return `${styles.weather_box} ${styles.cold}`;
};

React - Carousel with transitions between slides, react-spring

I've currently made a carousel component, I'm passing my data to it but I would like to add a transition between switching the data.
I've heard a lot of people recommending react-spring so I thought I'd give it a try. I'm currently trying to implement the useTransition hook, but it's not working as desired.
I simply want to fade in/out only the data when the user presses the "left" / "right" buttons. Currently it starts to fade my content, but then creates a clone of my carousel, and doesn't fade... I'm not sure what I'm doing wrong or if this is the correct hook I should be using?
Ultimately I would prefer a stagger animation when the content fades
in with some delay between each element. For example: the title, then price, then
desciption, etc, would fade in but with a 300ms delay between each
element.
Here's what I currently have:
const mod = (n: any, m: any) => ((n % m) + m) % m;
const CarouselContainer: React.FC = () => {
const [index, setIndex] = useState(0);
const handlePrev = useCallback(() => {
setIndex(state => mod(state - 1, data.length));
}, [setIndex]);
const handleNext = useCallback(() => {
setIndex(state => mod(state + 1, data.length));
}, [setIndex]);
const transitions = useTransition([index], (item: number) => item, {
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 }
});
return (
<>
{transitions.map(({ item, props, key }) => (
<Carousel
prevClick={handlePrev}
icon={data[item].icon}
title={data[item].title}
price={data[item].price}
description={data[item].description}
href={data[item].href}
nextClick={handleNext}
style={props}
key={key}
/>
))}
</>
);
};
There's quite a lot of code so I'm providing a CodeSandBox, forks are appretiated! :)
When the mount and unmount happens during the transition, there is a moment when the new and the old element is in the dom simultaneously. Most of the time I use absolute positioning with transition for this reason. This way the change blend in well.
For this you have to change to position: absolute here:
const StyledWrapper = styled.div`
position: absolute;
display: grid;
grid-template-columns: auto auto 20% 20% 1fr auto auto;
grid-template-rows: 3em 1fr auto 1fr 3em;
grid-gap: 0;
`;
I would try to add some movement to it:
const transitions = useTransition([index], (item: number) => item, {
from: { opacity: 0, transform: 'translateX(-420px)' },
enter: { opacity: 1, transform: 'translateX(0px)' },
leave: { opacity: 0, transform: 'translateX(420px)' }
});
And here is your modified code:
https://codesandbox.io/s/empty-rgb-xwqh1
Next you will want to handle the direction of the movement, according to the button pressed. Here is an example for that: https://codesandbox.io/s/image-carousel-with-react-spring-usetransition-q1ndd

Marquee Animation In Safari

I'm trying to recreate a marquee tag using CSS animations. Works fine in Chrome and Firefox, but for some reason in Safari(12.0.1), the animation either won't play or won't play correctly until I switch tabs.
Here's my CSS:
.marquee{
width:100%;
background-color:rgba(56, 38, 48, .8);
white-space:nowrap;
overflow:hidden;
box-sizing:border-box;
height:51px;
}
.marquee p {
display: inline-block;
padding-left: 100%;
animation: marquee 120s linear -3s infinite;
-webkit-animation: marquee 120s linear -3s infinite;
color: white;
}
#-keyframes marquee {
0% { transform: translate(0,0); }
100% { transform translate(-100%, 0); }
}
#-webkit-keyframes marquee {
0% { -webkit-transform: translate(0, 0); }
100% { -webkit-transform: translate(-100%, 0); }
}
HTML: This is a React application. Before the component mounts, it fetches a group of tickers and their values, then feeds them into a component that renders them:
componentWillMount(){
const params = {
method:'POST',
body:'key=1234567',
headers: {
"Content-type":"application/x-www-form-urlencoded; charset=UTF-8"
},
}
fetch('/tickers', params)
.then(res => res.json())
.then(data => {
const tickers = Object.assign({}, data)
this.setState({tickers})
console.log(this.state.tickers)
}
)
.catch(err => {
console.log(`could not fetch: ${err}`)
})
}
The tickers then get fed into this component where the marquee effect is being applied:
const Tickerbar = (props) => {
const displayNameStyle = {
color:'white',
}
const midStyle = {
color:'#CDBFBF',
marginRight:'20px'
}
const displayNames = Object.keys(props.tickers)
const displayNameAndMid = displayNames.map(cusip => {
return(
<span key={cusip}>
<span style={displayNameStyle}>
<b>{cusip.split('/').join('.')}:</b>
</span>
<span style={midStyle}>
<b>{(props.tickers[cusip]).toFixed(2)}</b>
</span>
</span>
)
})
return(
<div className="marquee">
<p>
{displayNameAndMid}
</p>
</div>
)
}
For reference, I am running macOS High Sierra 10.13.6
EDIT: I made a separate HTML markup using the same CSS, and it works fine. So I believe the problem has to do with getting the data from the database and rendering it to the component
EDIT: I seem to have some success adding by adding a 1 second delay to the animation time. I guess this gives the component time to fetch the tickers and render the animation.

Categories