Transitions with React - should I use TransitionGroup? - reactjs

I want page contents in my application to transition smoothly. I have been attempting to do this using react-transition-group but I have struggled to achieve the correct implementation. The following link was informative:
https://coursework.vschool.io/react-transitions-with-react-transition-group/
It shows how to make modularize and use TransitionGroup (although not both at the same time, unfortunately).
I created a demo project (based on the above link) to troubleshoot this issue. I have two items in an array ‘contactComponents’. All I am trying to do at the moment is make this information appear and disappear using the show/hide button.
Here is the main body of the code:
const contactDetails = ['Gryffindor Tower, Hogwarts','Gryffindor Tower, Hogwarts'];
const contacts = ['Harry', 'Ron'];
export default class App extends React.Component {
constructor(props){
super(props);
this.state = {
count: 0,
showMyContact: false
};
this.showContact = this.showContact.bind(this);
}
showContact() {
this.setState({showMyContact: !this.state.showMyContact})
}
render() {
const styles = {
container: { display: 'flex', justifyContent: 'center', width: '100vw', height: 100, flexDirection: 'column', padding: 100 },
btn: { width: '100%', display: 'flex', justifyContent: 'center'},
h1: { border: '2px solid blue', padding: 5, display: 'flex'}
};
let contactComponents = [contacts[this.state.count], contactDetails[this.state.count]];
console.log(this.state.showMyContact)
return (
<div>
<div style={ styles.container }>
<TransitionGroup component={null}>
{ contactComponents.map((item, key) =>
<CSSTransition
in={this.state.showMyContact}
key={key}
timeout={800}
classNames={"fade"}>
<h1 style={styles.h1}>
{
item
}
</h1>
</CSSTransition>
)}
</TransitionGroup>
<div style={ styles.btn }>
<button onClick={ this.showContact }>show/hide</button>
</div>
</div>
</div>
)
}
}
scss file:
.fade-appear,
.fade-enter {
opacity: 0;
z-index: 1;
}
.fade-appear-active,
.fade-enter.fade-enter-active {
opacity: 1;
transition: opacity 600ms linear 200ms;
}
.fade-exit {
opacity: 1;
}
.fade-exit.fade-exit-active {
opacity: 0;
transition: opacity 200ms linear;
}
Currently, the contents appears even though showMyContact is false when the render function first calls. Changing the state of showMyContact with the show/hide button has no effect. The content does not fade in and out as expected.
This post:
page transitions without React-Router
suggests it might be better to use pure css to carry out transitions rather than react-transition-group. Am I just barking up the wrong tree?

I found out that using pure css transitions provides the desired solution. I do not know if a solution using TransitionGroup and CSSTransition is feasible but it doesn't look like it.
By changing the contents of the render function to:
render() {
let contactComponents = [contacts[this.state.count], contactDetails[this.state.count]];
let cssList = [
"List",
this.state.showMyContact ? "ListShow" : "ListHide"
];
console.log(this.state.showMyContact);
return (
<div>
<div className={"container"}>
<List show={cssList.join(' ')} myContent={contactComponents}/>
<div className={"btn"}>
<button onClick={ this.showContact }>show/hide</button>
</div>
</div>
</div>
)
}
...and adding the following const:
const List = (props) => {
return (
<div className={props.show}>
<h1 className={"h1"}> { props.myContent[0] } </h1>
<h1 className={"h1"}> { props.myContent[1] } </h1>
</div>
)};
...and importing the following css file:
.container {
display: flex;
justify-content: center;
width: 500px;
height: 100px;
flex-direction: column;
padding: 100px;
}
.h1 {
border: 2px solid blue;
padding: 5px;
display: flex;
}
.btn {
width: 100%;
display: flex;
justify-content: center;
}
.List {
display: flex;
flex-direction: column;
transition: all 0.4s ease-out;
}
.ListShow {
opacity: 1;
}
.ListHide {
opacity: 0;
}
...I can get the desired behaviour.

Related

How to stop propagation of scroll event in case of a modal view?

I have this modal React component:
import React, { useEffect, useRef } from 'react'
import useOnClickOutside from '../../hooks/useOnClickOutside'
import Button from '../codrop/Button'
import styles from './Modal.module.scss'
var Scroll = require('react-scroll')
var scroll = Scroll.animateScroll
interface Props {
children?: React.ReactNode
title?: string
completion?: Function
[key: string]: any
isMyFlex?: Boolean
}
const Modal = ({ children, title, completion, isMyFlex, ...rest }: Props) => {
return (
<div className="overlay">
<div className="cnt-box cnt-call2 modal">
<div className="caption" style={{ paddingRight: 'unset' }}>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
}}
>
<h2 style={{ fontSize: '30px', fontWeight: '500' }}>{title}</h2>
<span
onClick={(e) => {
completion?.()
}}
style={{
fontFamily: 'Icons',
fontSize: '2rem',
cursor: 'pointer',
}}
>
c
</span>
</div>
{children}
</div>
</div>
</div>
)
}
export default Modal
And following two style:
.overlay {
position: fixed; /* Sit on top of the page content */
width: 100%; /* Full width (cover the whole page) */
height: 100%; /* Full height (cover the whole page) */
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5); /* Black background with opacity */
z-index: 2000; /* Specify a stack order in case you're using a different order for other elements */
overflow: hidden;
}
.modal {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 470px;
background-color: #fff;
border-radius: 6px;
padding: 30px 50px;
}
When modal / overlay is present, and I scroll in mobil, I see on the bottom, as some pixlel is visilbe from background, that the background i scrolling, though not the foreground.
Is it a way to prevent this scrolling?
I tried to add overflow: hidden; to the overlay style, did not help.
I tried hide overflow when component appears, but has no effect, why?
const Modal = ({ children, title, completion, isMyFlex, ...rest }: Props) => {
useEffect(() => {
document.body.style.overflow = 'hidden'
return () => {
document.body.style.overflow = 'unset'
}
}, [])
By default, trying to scroll an element that has already reached the bottom will scroll the parent. You can use the overscroll-behavior CSS property on the modal to prevent this behavior:
overscroll-behavior: contain;
See https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior for the documentation.

Button doesn't disappear immediately; need to move mouse during animation using clip-path

I am setting the clipPath property from circle(0%) to circle(100%) using GSAP timeline.
let t1 = useRef();
useEffect(() => {
t1.current = gsap.timeline({
defaults: { duration: 0.5, ease: "Back.easeOut.config(2)" },
});
t1.current.paused(true); //to ensure animation doesn't play immediately
t1.current.to(".overlay", { clipPath: "circle(100%)" });
});
const handleClick = () => {
t1.current.play(); //start the animation
};
const handleClose = () => {
t1.current.reverse(0.2); //reverse the animation from 0.2 seconds
};
Complete React Component code:
import React, { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faWindowClose } from "#fortawesome/free-solid-svg-icons";
export default function GSAPFullScreen() {
let t1 = useRef();
useEffect(() => {
t1.current = gsap.timeline({
defaults: { duration: 0.5, ease: "Back.easeOut.config(2)" },
});
t1.current.paused(true); //to ensure animation doesn't play immediately
t1.current.to(".overlay", { clipPath: "circle(100%)" });
}, []);
const handleClick = () => {
t1.current.play(); //start the animation
};
const handleClose = () => {
t1.current.reverse(0.2); //reverse the animation from 0.2 seconds
};
return (
<>
<div
className="overlay"
style={{
clipPath: "circle(0%)",
width: "100%",
height: "100%",
position: "fixed",
overflowY: "scroll",
overflowX: "hidden",
backgroundColor: "purple",
}}
>
<FontAwesomeIcon
icon={faWindowClose}
size="2x"
style={{
position: "absolute",
top: "2rem",
right: "2rem",
color: "white",
cursor: "pointer",
}}
onClick={handleClose}
/>
<div className="container md" style={{ color: "white" }}>
<br />
<div style={{ fontWeight: "bold" }}>This is an amazing Question</div>
<div>What is your question? Can you guess?</div>
<div>Option 1</div>
<div>Option 2</div>
<div>Option 3</div>
<div>Option 4</div>
</div>
</div>
<div className="container" style={{ height: "100vh" }}>
<div className="flex">
<button className="lg p-1 btn" onClick={() => handleClick()}>
Launch Animation
</button>
</div>
</div>
</>
);
}
Relevant CSS:
.container {
max-width: 1100px; /* Ensures heading is in center beyond 1100px*/
margin: 0 auto; /* Ensures to keep the 1100px container in middle of the screen;
until 1100px it will be on the side and this property will not have any affect*/
overflow: auto; /* This removes the space on the top of the heading which was created because of margin: 10px 0 on h1*/
padding: 0 40px;
}
.btn {
display: inline-block;
padding: 10px 30px;
cursor: pointer;
background: var(--primary-color);
color: #fff;
border: none;
border-radius: 5px;
}
.md {
font-size: 2rem;
}
.lg {
font-size: 3rem;
}
.flex {
display: flex;
justify-content: center; /* aligns along the main axis*/
align-items: center;
height: 100%;
}
.p-1 {
padding: 1rem; /*1 rem is usually 16px depending the size at root*/
}
.btn:hover:enabled{
transform: scale(0.98); /*reduces the size of button a bit*/
}
When the button has the pseudo class :hover a transform will be applied to the element, which means that it the stacking context is changed (see also Stacking without the z-index property).
To fix this you can add z-index: 1 to the overlay class or remove the transform from the :hover class (Not ideal).

ReactDOM.createPortal modal is mounted on DOM but nothing is displayed on the screen

this is a typescript-next.js project. I have this Modal component:
interface ModalProps {
onCancelModal: () => void;
onAcceptModal: () => void;
acceptEnabled: boolean;
isLoading?: boolean;
title: string;
}
const Modal: React.FC<ModalProps> = (props) => {
let containerRef = useRef<HTMLDivElement | null>(null);
console.log("container", containerRef);
useEffect(() => {
const rootContainer = document.createElement("div");
const parentElem = document.querySelector("#__next");
parentElem?.insertAdjacentElement("afterend", rootContainer);
if (!containerRef.current) {
containerRef.current = rootContainer;
}
return () => rootContainer.remove();
}, []);
return containerRef.current
? ReactDOM.createPortal(
<div className="modal">
<header className="modal__header">
<h1>{props.title}</h1>
</header>
<div className="modal__content">{props.children}</div>
<div className="modal__actions">
<Button design="danger" mode="flat" onClick={props.onCancelModal}>
Cancel
</Button>
<Button
mode="raised"
onClick={props.onAcceptModal}
disabled={!props.acceptEnabled}
loading={props.isLoading}
>
Accept
</Button>
</div>
</div>,
containerRef.current
)
: null;
};
export default Modal;
I pass a custom error to ErrorHandler component:
const ErrorHandler: React.FC<ErrorHandlerProps> = (props) => (
<Fragment>
{props.error && <Backdrop onClick={props.onHandle} />}
{props.error && (
<Modal
title="An Error Occurred"
onCancelModal={props.onHandle}
onAcceptModal={props.onHandle}
acceptEnabled
>
<p>{props.error}</p>
</Modal>
)}
</Fragment>
);
However, Modal component is successfully mounted on the DOM but nothing displays on the screen.
EDIT
I have backdrop and modal components.
// css for backdrop
.backdrop {
width: 100%;
height: 100vh;
background: rgba(0, 0, 0, 0.75);
z-index: 100;
position: fixed;
left: 0;
top: 0;
transition: opacity 0.3s ease-out;
opacity: 1;
}
// css for Modal
.modal {
position: fixed;
width: 90%;
left: 5%;
top: 20vh;
background: white;
border-radius: 5px;
z-index: 200;// I changed this to 999999 but didnot solve the issue
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
}
.modal__header {
border-bottom: 2px solid #3b0062;
}
.modal__header h1 {
font-size: 1.5rem;
color: #3b0062;
margin: 1rem;
}
.modal__content {
padding: 1rem;
}
.modal__actions {
padding: 1rem;
text-align: right;
}
.modal__actions button {
margin: 0 0.5rem;
}
#media (min-width: 768px) {
.modal {
width: 40rem;
left: calc((100% - 40rem) / 2);
}
}
I found the answer after i refresh my memory. I realized that there is another .modal className on elements-styles tab. It points me to the /node_modules/bootstrap/scss/_modal.scss file which also has modal className and it was overriding my custom className.
.modal {
position: fixed;
top: 0;
left: 0;
z-index: $zindex-modal;
display: none;
width: 100%;
height: 100%;
overflow: hidden;
// Prevent Chrome on Windows from adding a focus outline. For details, see
// https://github.com/twbs/bootstrap/pull/10951.
outline: 0;
// We deliberately don't use `-webkit-overflow-scrolling: touch;` due to a
// gnarly iOS Safari bug: https://bugs.webkit.org/show_bug.cgi?id=158342
// See also https://github.com/twbs/bootstrap/issues/17695
}

How to open a multiple message box with ReactJS

I'm building a multiple chatbox messaging like Facebook's popup messenger windows. At the moment, the user can call up a chatbox, close it, minimize it, etc.
When I click on multiple users, say 3, I'm supposed to have three chatboxes pop up corresponding to those three different users. Currently, only one chatbox appears.
This screenshot illustrates what I want to achieve. On each user's button click, it's own chatbox will popup.
Here is the demo and download link for a jQuery equivalent: link.
This is the full React code which shows just one chat box:
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
import axios from 'axios';
import './style.css';
class MessageBox extends Component {
constructor() {
super();
this.state = {
showBox: false,
shownToggle: true,
data: [
{ id: "1", name: "Tony" },
{ id: "2", name: "Mark" },
{ id: "3", name: "Joy" }
],
currentRec: undefined,
};
this.showBox = this.showBox.bind(this);
this.closeBox = this.closeBox.bind(this);
this.toggle = this.toggle.bind(this);
}
showBox = (i, pid, name) => {
this.setState({ currentRec: i });
console.log(`Selected record index: ${i}`);
alert(pid);
alert(name);
this.setState({ showBox: true }, () => {
document.addEventListener('click', this.closeBox);
});
}
closeBox(event) {
if (this.dropdownBox.contains(event.target)) {
this.setState({ showBox: false }, () => {
document.removeEventListener('click', this.closeBox);
});
}
}
toggle() {
this.setState({
shownToggle: !this.state.shownToggle
});
}
render() {
var hidden = {
display: this.state.shownToggle ? "block" : "none"
}
return (
<div>
<ul style={{ float: "right" }}>
{this.state.data.map((person, i) => (
<div className="chat-sidebar" key={i}>
<button onClick={() => this.showBox(i, person.id, person.name)}>Chat with {person.name}</button>
{this.state.showBox ? (
<div className="msg_box" style={{ right: '270px' }}>
<div onClick={this.toggle.bind(this)} class="msg_head">
(<b style={{ color: 'orange' }}>
{this.state.currentRec !== undefined &&
<div className="modal-body">
{this.state.data[this.state.currentRec].name}
({this.state.data[this.state.currentRec].id})
</div>
}
</b>)
Minimize
<div className="close" ref={(element) => { this.dropdownBox = element; }} style={{ color: 'white' }}>Close</div>
</div>
<div style={hidden} className="msg_wrap"><div className="msg_body">Message will appear here</div></div>
</div>) : (null)}
</div>
))}
</ul>
</div>
);
}
}
/****** Chat Popup Layout ******/
body{
background: #e5e5e5;
font-family: sans-serif;
}
.msg_box{
position: fixed;
bottom: -5px;
width: 250px;
background: white;
border-radius: 5px 5px 0px 0px;
}
.msg_head{
background: black;
color: white;
padding: 8px;
font-weight: bold;
cursor: pointer;
border-radius: 5px 5px 0px 0px;
}
.msg_body{
background: white;
height: 200px;
font-size: 12px;
padding: 15px;
overflow: auto;
overflow-x: hidden;
}
.close{
float: right;
cursor: pointer;
}
.minimize{
float: right;
cursor: pointer;
padding-right: 5px;
}
/****** Slider Layout Popup ******/
.chat-sidebar {
width: 250px;
height: 100%;
right: 0px;
top: 0px;
padding-top: 10px;
padding-bottom: 10px;
border: 1px solid #b2b2b2;
}
After series of efforts, the use of jquery and Reactjs is what solved my issue as per code below
const dataSet = this.state.data;
alert(dataSet);
if ($.inArray(pid, dataSet) != -1)
{
dataSet.splice($.inArray(pid, this.state.data), 1);
}
dataSet.unshift(pid);
var s = 270 ; // start position
var j = 260; //next position
$.each(dataSet, function( index, value ) {
if(index < 4){
$('[rel="'+value+'"]').css("right",s);
$('[rel="'+value+'"]').show();
s = s+j;
}
else{
$('[rel="'+value+'"]').hide();
}

React-modal hides behind elements

I am trying to make use of react-modal for the first time. When I click on the sign-in button, the react-modal component is invoke but seems to be hiding behind the cover page which is a video landing page.
The React devtool displays the appropriate states before the sign-in button is clicked
before the sign-in button is clicked
When the sign-in button is now clicked, the react devtool now displays that the ModalPortal component is rendered showing the appropriate states
when the sign-in button is clicked
SignInModal.scss
.ReactModalPortal>div {
opacity: 0;
}
.ReactModalPortal .ReactModal__Overlay {
align-items: center;
display: flex;
justify-content: center;
transition: opacity 200ms ease-in-out;
}
.ReactModalPortal .ReactModal__Overlay--after-open {
opacity: 1;
}
.ReactModalPortal .ReactModal__Overlay--before-close {
opacity: 0;
}
.modal {
position: relative;
background: #464b5e;
color: white;
max-width: 90rem;
outline: none;
padding: 3.2rem;
text-align: center;
}
.modal__title {
margin: 0 0 1.6rem 0;
}
.modal__body {
font-size: 2rem;
font-weight: 300;
margin: 0 0 3.2rem 0;
word-break: break-all;
}
CoverPage.js Component
import Header from './Header';
import HeaderVideo from './HeaderVideo';
import SignInModal from './SignInModal';
import React, { Component } from 'react';
class CoverPage extends Component {
state = {
modalIsOpen: false
};
onOpenModal = () => {
this.setState(() => ({
modalIsOpen: true
}));
};
onCloseModal = () => {
this.setState(() => ({
modalIsOpen: false
}));
};
render() {
return (
<div>
<Header />
<HeaderVideo onOpenModal={this.onOpenModal} />
<SignInModal
modalIsOpen={this.state.modalIsOpen}
onOpenModal={this.onOpenModal}
onCloseModal={this.onCloseModal}
/>
</div>
);
}
}
export default CoverPage;
HeaderVideo.js Component
import React from 'react';
import Signup from './Signup';
import CoverInfo from './CoverInfo';
const HeaderVideo = props => {
return (
<div className="video-container">
<video preload="true" autoPlay loop volume="0" postoer="/images/1.jpg">
<source src="images/vine.mp4" type="video/mp4" />
<source src="images/vine1.webm" type="video/webm" />
</video>
<div className="video-content">
<div className="container content">
<div className="row">
<div className="col-md-9">
<CoverInfo onOpenModal={props.onOpenModal} />
</div>
<div className="col-md-3">
<Signup />
</div>
</div>
</div>
</div>
</div>
);
};
export default HeaderVideo;
CoverInfo.js Component
import React from 'react';
const CoverInfo = props => {
return (
<div className="info">
<div>
<h1>Welcome to EventCity!</h1>
</div>
<div>
<p>
At EventCity! we pride ourselves on the unrivalled personal {`event`} services,we provide
to our clientele. We guide you from the stressful decision making {`process`},ensuring you
are comfortable,whether it is a wedding, corporate {`function `}or even a kiddies party,we
create a buzz around you, taking you to the next level.
</p>
</div>
<div>
<h3>Innovation, {`Performance`} and Delivery</h3>
</div>
<button type="button" className="btn btn-success btn-lg" onClick={props.onOpenModal}>
Sign In here
</button>
</div>
);
};
export default CoverInfo;
video-cover.scss
video {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
min-width: 100%;
min-height: 100%;
width: auto;
height: auto;
z-index: 1;
}
.video-content {
z-index: 2;
position: absolute;
background: rgba(0, 0, 0, 0.6);
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.content {
padding-top: 120px;
}
You need to set the z-index property on the Modal's overlay, which normally has a z-index of 0. The CSS class is .ReactModal__Overlay
Here is the pure-React way of doing it:
const customStyles = {
content : {
...
},
overlay: {zIndex: 1000}
};
<Modal style={customStyles}>
...
</Modal>
.modal {
position: fixed;
z-index:9999;
top :0;
left:0;
right:0;
bottom:0;
background: #464b5e;
color: white;
outline: none;
padding: 3.2rem;
text-align: center;
}
Example of react-modal inline styles Set the styles in the react-modal inline styles. The z-index to 100 but make just like below
style={{
overlay: {
zIndex: 100,
backgroundColor: 'rgba(70, 70, 70, 0.5)',
},

Resources