How to preserve CSS transition in react child component - javascript

I'm facing this issue in which if I don't wrap an element with CSS transition into a React child component then it runs the transition smoothly and if I wrap it into a child component it doesn't show the transition at all.
App.js
...
const [pb, setPb] = React.useState("0");
if (pb == 0) {
setTimeout(() => {
setPb("60");
}, 1000);
}
const AnotherBall = () => {
return (
<div
className="ball2"
style={{
top: `${pb}%`
}}
></div>
);
};
return (
<div className="App">
<div
className="ball"
style={{
top: `${pb}%`
}}
></div>
<AnotherBall />
</div>
);
style.css
...
.ball,
.ball2 {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
background: #ff5f5f;
border-radius: 50%;
transition: all 1s ease-in-out;
}
.ball2 {
left: 90%;
background: #ff5f5f;
}
sandbox: https://codesandbox.io/s/boring-wescoff-jcxgw?file=/src/App.js

Don't declare components inside the body of other components. Every time a component rerenders the functions it contains are redeclared, meaning they are not referentially stable from one render to the next. AnotherBall in your example is being unmounted and remounted with the new value because React thinks it's a new component.
Instead, move the component outside of App and pass the required value as a prop like this:
// App.js
<AnotherBall pb={pb} />
// AnotherBall.js
const AnotherBall = ({pb}) => {
return (
<div
className="ball2"
style={{
top: `${pb}%`
}}
></div>
);
};

Related

React Slick slider, current active slide overlapped by next slide

I'm currently using Slick in order to make a carousel.
I'm having two issues right now, let's start with the first one.
1)
I'm currently using a slider in which i want to show 3 slides: the current image (Spyro), the previous one (Crash) and the next one (Tekken).
As you see, while the current slide correctly overlaps the previous one (Spyro > Crash), the next one overlaps the current slide (Tekken > Spyro).
Of course i want the current slide to be on top of both of them... How can i fix this?
I'm attacching the code below.
App.js
import "./App.css";
import { useEffect, useState } from "react";
import Slider from "react-slick";
import SliderData from "./SliderData";
import { AiOutlineArrowLeft, AiOutlineArrowRight } from "react-icons/ai";
function useWindowSize() {
const [size, setSize] = useState([window.innerHeight, window.innerWidth]);
useEffect(() => {
const handleResize = () => setSize([window.innerHeight, window.innerWidth]);
window.addEventListener("resize", handleResize);
}, [])
return size;
}
const array = SliderData.map((x) => {
return x.image;
})
console.log(array);
function App() {
const NextArrow = ({ onClick }) => {
return (
<div className="arrow next" onClick={onClick}>
<AiOutlineArrowRight />
</div>
);
};
const PrevArrow = ({ onClick }) => {
return (
<div className="arrow prev" onClick={onClick}>
<AiOutlineArrowLeft />
</div>
);
};
const [imageIndex, setImageIndex] = useState(0);
const [height, width] = useWindowSize();
const settings = {
className: "center",
infinite: true,
lazyLoad: true,
speed: 300,
slidesToShow: width > 1000 ? 3: 1,
centerMode: true,
centerPadding: "60px",
nextArrow: <NextArrow />,
prevArrow: <PrevArrow />,
beforeChange: (current, next) => {
console.log(current);
setImageIndex(next);
}
};
return (
<div className="App">
<Slider {...settings}>
{array.map((img, idx) => (
<div className={idx === imageIndex ? "slide activeSlide" : "slide"}>
<img src={img} alt={img} />
</div>
))}
</Slider>
</div>
);
}
export default App;
App.css
#import "~slick-carousel/slick/slick.css";
#import "~slick-carousel/slick/slick-theme.css";
.App {
width: 100%;
margin: 10rem auto;
height: 570px;
}
.slide img {
width: 35rem;
align-items: center;
margin: 0 auto;
z-index: 1;
}
.slide {
transform: scale(0.8);
transition: transform 300ms;
opacity: 0.5;
z-index: -1;
}
.activeSlide {
transform: scale(1.1);
align-items: center;
opacity: 1;
}
.arrow {
background-color: #fff;
position: absolute;
cursor: pointer;
z-index: 10;
}
.arrow svg {
transition: color 300ms;
}
.arrow svg:hover {
color: #68edff;
}
.next {
right: 3%;
top: 50%;
}
.prev {
left: 3%;
top: 50%;
}
SliderData.js
const SliderData = [
{
image:
"https://www.spaziogames.it/wp-content/uploads/2020/06/Crash-4-Pirate_06-29-20.jpg"
},
{
image:
"https://d2skuhm0vrry40.cloudfront.net/2018/articles/2018-07-18-14-24/news-videogiochi-spyro-reignited-trilogy-video-di-gameplay-livello-colossus-1531920251281.jpg/EG11/thumbnail/750x422/format/jpg/quality/60"
},
{
image: "https://i.ytimg.com/vi/OUh82pOFGDU/maxresdefault.jpg"
},
{
image: "https://www.psu.com/wp/wp-content/uploads/2020/07/MetalGearSolidRemake-1024x576.jpg"
}
];
export default SliderData;
2)
As you see, the active slide is not perfectly centered. Since i suck in CSS i did not use the display: flex; command.
What do you suggest? how can i fix this?
Thank you all.
You need to apply a position element to .slide for the z-index to work properly.
Note: z-index only works on positioned elements (position: absolute, position: relative, position: fixed, or position: sticky) and flex items (elements that are direct children of display:flex elements).
You can read more on z-index here
This is my answer for this issue. Basically you should set position and z-index for every item and set higher z-index for current active item
.App .slick-slide {
position: relative;
z-index: 1;
/* your choice, but make sure z-index of active slide is higher than this value */
}
.App .slick-slide.slick-current {
z-index: 10;
}

React Context - "this" is undefined

I am using React Context in order to manage a global state.
So I have defined my Context with its Provider and its Consumer.
I have my videoplaying-context.js
import React from "react";
import { createContext } from 'react';
// set the defaults
const VideoContext = React.createContext({
videoPlaying: false,
setPlayingVideo: () => {}
});
export default VideoContext;
In my _app.js I have:
import App from 'next/app'
import { PageTransition } from 'next-page-transitions'
import VideoContext from '../components/videoplaying-context'
class MyApp extends App {
setPlayingVideo = videoPlaying => {
this.setState({ videoPlaying });
};
state = {
videoPlaying: false,
setPlayingVideo: this.setPlayingVideo
}
render() {
console.log('new _app.js defalt page');
const { Component, pageProps, router, state } = this.props
return (
<React.Fragment>
<VideoContext.Provider value={this.state}>
<PageTransition timeout={300} classNames="page-transition">
<Component {...pageProps} key={router.route} />
</PageTransition>
</VideoContext.Provider>
</React.Fragment>
)
}
}
export default MyApp
and then in one of my file I have put the Consumer:
import Layout from "../components/Layout";
import ReactPlayer from 'react-player
import VideoContext from '../components/videoplaying-context'
class Video extends React.Component {
constructor(props) {
super(props);
this.triggerVideo = this.triggerVideo.bind(this);
}
triggerVideo(event) {
console.log("click");
/* doing other stuff here... */
}
render() {
return (
<VideoContext.Consumer>
{context => (
<Layout>
<h1>Videos</h1>
<div>
<div className="group" id="process-video">
<div
className="poster-image"
onClick={() => {
this.triggerVideo.bind(this);
context.setPlayingVideo(true);
}}
/>
<ReactPlayer
url="https://vimeo.com/169599296"
width="640px"
height="640px"
config={{
vimeo: {
playerOptions: {
thumbnail_url: "http://placehold.it/640x640.jpg",
thumbnail_width: 640,
thumbnail_height: 640
}
}
}}
/>
</div>
</div>
<style jsx global>{`
.group {
position: relative;
height: 0;
overflow: hidden;
height: 640px;
width: 640px;
}
.poster-image {
background: url("http://placehold.it/640x640.jpg") center center;
background-size: cover;
bottom: 0;
left: 0;
opacity: 1;
position: absolute;
right: 0;
top: 0;
z-index: 10;
height: 640px;
width: 640px;
transition: all 0.4s ease-in;
}
.poster-image + div {
position: absolute;
top: 0;
left: 0;
width: 640px;
height: 640px;
}
.poster-image.video--fadeout {
opacity: 0;
}
`}</style>
</Layout>
)}
</VideoContext.Consumer>
);
}
}
export default Video;
So, the function "context.setPlayingVideo(true)" is working fine and it's correctly setting the global state "videoPlaying" to true, but, after the introduction of the Context, "this.triggerVideo.bind(this);" is not working anymore because "this" is undefined.
I tried removing it and other stuff but I'm really stuck and I don't know hot to fix it.
Thanks everyone!
On this line you are not calling the method triggerVideo
onClick={() => { this.triggerVideo.bind(this); context.setPlayingVideo(true); }}
Change to:
onClick={() => { this.triggerVideo(); context.setPlayingVideo(true); }}
or to:
onClick={() => { this.triggerVideo.bind(this)(); context.setPlayingVideo(true); }}

React hooks onClick fire only for parent (modal)

I have a modal window and i want it to hide if user clicked on modal itself(black background), but click is trigerring by clildrens to. Here is the example:
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
export function Modal({ show, children }) {
return (
<DivModal show={show}>
{children}
</DivModal>
)
}
const DivModal = styled.div`
display: ${props => (props.show ? 'block' : 'none')};
`
How to fire event's only for modal itself?
Ehh, this work, but if you click on wraper div - modal will not hide :(
After some googling ant try, found a way. Key point is that you need to stop onClick event propagation from parent to childrens. In my case i just wrapped my component by div with onClick={e => e.stopPropagation()}.
import React from 'react'
import styled from 'styled-components'
export function Modal({ show, showModalSet, children }) {
return (
<DivModal onClick={() => showModalSet(false)} show={show}>
<div onClick={e => e.stopPropagation()}>{children}</div>
</DivModal>
)
}
const DivModal = styled.div`
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: ${({ show }) => (show ? 'block' : 'none')};
z-index: 10;
overflow-y: scroll;
`
Component use:
<Modal show={showModal} showModalSet={showModalSetFunctionInUsePlace}>
Another approach, that i'am happy now with is to use refs and compare what component was clicked:
import React, { useRef } from 'react'
import styled from 'styled-components'
export function Modal({ show, showModalSet, children }) {
const modalRef = useRef(null)
function handleClick(e) {
if (e.target == modalRef.current) showModalSet(false)
}
return (
<DivModal onClick={handleClick} show={show} ref={modalRef}>
{children}
</DivModal>
)
}
const DivModal = styled.div`
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: ${({ show }) => (show ? 'block' : 'none')};
z-index: 10;
overflow-y: scroll;
`

bootstrap modal behaviour issue (react)

I am using a bootstrap modal for my application. It works fine in the beginning, but once I open the menu bar and close it, modal does not open anymore nor the backdrop does not go away even though I click it.
I have screenshots for better understanding.
1.landing page
2.testing modal(working)
3.open side menu bar
4.after closing the side menu, try opening the modal again(not working)
To open the menu bar, I use transform: translateX() on body tag so technically the whole body moves side to side to show the menu.
If I comment out the transform: translateX(), modal works fine even after I click the menu bar(it obviously does not show the menu, but other css properties work the same except transform).
So I found out transforming property causes the problem with the modal, but I don't understand why and how to fix it.
EDITTED ( code added ):
import 'App.css';
class App extends Component {
constructor(props) {
super(props);
this.menu_bar = false;
}
toggleMenu = () => {
const app = document.querySelector('.App');
if( this.menu_bar ){
app.style.transform = 'translateX(0)';
app.classList.remove('coverApp');
this.menu_bar = false;
}else {
app.style.transform = 'translateX(300px)';
app.classList.add('coverApp');
this.menu_bar = true;
}
}
hideMenu = (e) => {
const app = document.querySelector('.App');
if( e.target.classList.contains('coverApp') ){
app.style.transform = 'translate(0)';
app.classList.remove('coverApp');
this.menu_bar = false;
}
}
render() {
return (
<div className="App" onClick={this.hideMenu}>
<Navbar toggleMenu={this.toggleMenu}/>
<LeftMenuBar toggleMenu={this.toggleMenu}/>
<Route path="/" exact component={HomePage}/>
</div>
)
}
}
function Navbar(props) {
const { toggleMenu } = props;
return (
<nav className="Navbar">
<span>
<div id="nav-toggle" onClick={toggleMenu} >☰</div>
...
</span>
</nav>
)
}
function LeftMenuBar(props) {
const { toggleMenu } = props;
return (
<main className="LeftMenuBar">
<div className="content">
<div className="controller">
<p>ENGLISH</p>
<div className="btn-close" onClick={toggleMenu}>CLOSE</div>
...
</div>
</div>
</main>
)
}
class HomePage extends Component {
render() {
return (
<main className="HomePage">
<button type="button" id="modal-btn" className="btn btn-primary" data-toggle="modal" data-target="#home_modal" aria-label="close">
LETS START!
</button>
<div className="HomeModal">
<div id="home_modal" className="modal" tabIndex="-1" role="dialog" aria-labelledby="home_modal">
<div className="modal-dialog" role="document" style={{marginTop: '150px', maxWidth: '750px'}}>
<div className="modal-content">
<div className="modal-body" style={{backgroundColor: 'white'}}>...</div>
</div>
</div>
</div>
</div>
</main>
)
}
}
CSS
body {
overflow: hidden;
}
.App {
transition: transform 0.5s;
}
.coverApp::before {
content: '';
opacity: 1;
position: absolute;
width: 100vw;
height: 100vh;
left: 0;
top: 0;
z-index: 300;
background-color: rgba(0, 0, 0, .75);
transition: all 0.5s ease-in-out;
}
.LeftNavbar {
position: fixed;
background: rgba(50, 50, 50);
color: #f1f1f1;
width: 300px;
height: 100vh;
top: 0;
left: -300px;
border-right: solid 0.2px lightgray;
}
#modal-btn {
position: absolute;
top: 350px;
left: 500px;
}
.HomeModal {
position: absolute;
top: 350px;
left: 700px;
}

react get div element size incorrect

I found some relevant questions with this but none of them can't solve my problem. So, I wrote this post. If there are any relevant threads, please let me know.
I am trying to get the size (in px unit) of div element so that I can draw some SVG groups inside of it. For that, I wrote following React class after searching the web for a while.
class ChartBox extends React.Component {
constructor() {
super();
this.state = {width: 0, height: 0}
}
componentDidMount() {
window.addEventListener('resize', () => this.handleResize());
this.handleResize();
}
componentWillUnmount() {
window.removeEventListener('resize', () => this.handleResize());
}
handleResize = () => {
this.setState({
width: this.container.offsetWidth,
height: this.container.offsetHeight
});
}
render() {
return (
<div className={this.props.className}>
<div className={theme.up}>
<div className={theme.left}>up.left</div>
<div className={theme.right}
ref={c => this.container = c}>
<p>up.right</p>
<p>`${this.state.width}x${this.state.height}`</p>
</div>
</div>
<div className={theme.down}>
<div className={theme.left}> down.left </div>
<div className={theme.right}>down.right</div>
</div>
</div>
);
}
}
The ChartBox class get a style of the outer-most div element from a parent React element. And for all inner div elements in the ChartBox class, I import following css.
:root {
--right-width: 100px;
--top-height: 100px;
--left-width: calc(100% - var(--right-width));
--bottom-height: calc(100% - var(--top-height));
}
.up {
float: left;
width: 100%;
height: var(--top-height);
padding: 0px
}
.bottom {
float: left;
width: 100%;
height: var(--bottom-height);
padding: 0px
}
.left {
float: left;
width: var(--left-width);
height: 100%;
padding: 0px
}
.right {
float: left;
width: var(--right-width);
height: 100%;
padding: 0px
}
As you can imagine, I am trying to divide the outer-most div element into four sections where the smallest div element has a size of 100px by 100px.
First of all, all elements are mounted correctly when I checked it visually. However, the returned values are incorrect. For example, when I first reload the page, it returns 762 x 18 that is incorrect. But after resizing the window, it returns correct size as 100 x 100.
Any suggestions or comments to solve this issue?
I had a similar issue. I had to use setTimeout(0) for this. For example:
handleResize = () => {
setTimeout(() => {
this.setState({
width: this.container.offsetWidth,
height: this.container.offsetHeight
});
}, 0 );
}
This ensures that the function call happens at the end of the call stack and after the container has fully rendered.
Update:
I actually found a better solution using getSnapshotBeforeUpdate. No set timeout is needed with this. From the React docs: "It enables your component to capture some information from the DOM (e.g. scroll position) before it is potentially changed."
For example:
getSnapshotBeforeUpdate() {
let width = this.container.offsetWidth;
let height = this.container.offsetHeight;
return { width, height };
}
componentDidUpdate( prevProps, prevState, snapshot ) {
if (prevState.width !== snapshot.width || prevState.height !==
snapshot.height) {
this.setState({
height: snapshot.height,
width: snapshot.width,
})
}
}

Categories