CSSTransition from react-transition-group not applying classes - reactjs

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.

Related

CSSTransition not working with React Portals

On this StackBlitz: https://stackblitz.com/edit/csstransition-test
I have a project that open a green div with a zoom-in effect:
/src/App.js
import React, { useState } from 'react';
import './style.css';
import Modal from './Modal';
export default function App() {
const [ isOpen, setIsOpen ] = useState(false);
const handleClick = () => {
setIsOpen(!isOpen);
};
return (
<div>
<h1>Hello StackBlitz!</h1>
<p>Start editing to see some magic happen :)</p>
<button onClick={handleClick}>Click Me</button>
<br /><br />
<Modal isOpen={isOpen}>
<div>Hello World</div>
</Modal>
</div>
);
}
/src/Portal.js
import { Component } from 'react';
import ReactDOM from 'react-dom';
const portal = document.getElementById('portal');
export default class Portal extends Component {
render() {
return ReactDOM.createPortal(this.props.children, portal);
}
}
/src/Modal.jsx
import React, { useRef } from "react";
import { CSSTransition } from "react-transition-group";
import Portal from "./Portal";
export default function Modal({ isOpen, children }) {
const container = useRef(null);
return (
<CSSTransition
nodeRef={container}
in={isOpen}
timeout={300}
classNames="modal"
unmountOnExit
onEnter={ () => console.log('entered') }
onExited={ () => console.log('exited') }
>
<div ref={container} style={{
display: 'inline-block',
backgroundColor: '#0f0',
padding: '50px',
}}>
{children}
</div>
</CSSTransition>
);
}
/src/style.css
h1,
p {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
.modal-enter {
transform: scale(0);
}
.modal-enter-active {
transform: scale(1);
transition: transform 300ms;
}
.modal-exit {
transform: scale(1);
}
.modal-exit-active {
transform: scale(0);
transition: transform 300ms;
}
My problem is: I need to make it work with Portals.
On file: /src/Modal.jsx, if I replace the div inside: CSSTransition with: Portal then I get errors.
This is the code with the change:
/src/Modal.jsx
import React, { useRef } from "react";
import { CSSTransition } from "react-transition-group";
import Portal from "./Portal";
export default function Modal({ isOpen, children }) {
const container = useRef(null);
return (
<CSSTransition
nodeRef={container}
in={isOpen}
timeout={300}
classNames="modal"
unmountOnExit
onEnter={ () => console.log('entered') }
onExited={ () => console.log('exited') }
>
<Portal ref={container} style={{
display: 'inline-block',
backgroundColor: '#0f0',
padding: '50px',
}}>
{children}
</Portal>
</CSSTransition>
);
}
Please try the StackBlitz: https://stackblitz.com/edit/csstransition-test and you are also welcomed to post your own forked StackBlitz with the fixes.
Thanks!
I think this is happing because CSSTransition component is being created in root element. And the model is created in your portal element. This CSSTransition component is not wrapping anything.
You should instead wrap the whole Model with the portal, like this:
<Portal>
<CSSTransition
nodeRef={container}
in={isOpen}
timeout={300}
classNames="modal"
unmountOnExit
onEnter={ () => console.log('entered') }
onExited={ () => console.log('exited') }
>
<div ref={container} style={{
display: 'inline-block',
backgroundColor: '#0f0',
padding: '50px',
}}>
{children}
</div>
</CSSTransition>
</Portal>
I hope this helps.

react styled component created dynamically

I am new to styled Components from react and I am having trouble creating dynamically a clock with has an initial degree for minutes(minute degrees) and an initial degree for hours (hourDegrees).
This is so far what I have achieved, but I get the following message:
Keyframes.js:20 The component styled.div with the id of "..." has been created dynamically.
You may see this warning because you've called styled inside another component.
To resolve this only create new StyledComponents outside of any render method and function component.
APP CODE
function App() {
return (
<div className="App">
<main>
<div className="clock-wrap">
<section className="firstMinute">
{numbers[0].map((coord, index) => {
return (
<Clock
key={index}
hourDegrees={coord[0]}
minuteDegrees={coord[1]}
/>
);
})}
</section>
<section className="secondMinute">
{numbers[1].map((coord, index) => {
return (
<Clock
key={index}
hourDegrees={coord[0]}
minuteDegrees={coord[1]}
/>
);
})}
</section>
</div>
</main>
</div>
);
}
But I can't solve this issue as, from what I understand, I have created separated components with the info passed as props.
import styled from 'styled-components'
export default function Clock ({ minuteDegrees, hourDegrees, index}) {
const finalHourDegrees = Number(hourDegrees + 360)
const finalMinuteDegrees = Number(minuteDegrees + 360)
const StyledClock = styled.div`
width: 10vh;
`
//hour
const animationHour = keyframes`
from {
transform: rotate(${props => props.hourDegrees}deg);
}
to {
transform: rotate(${finalHourDegrees}deg);
}
`
const HourStyled = styled.div`
animation: ${animationHour} 4s ease-out infinite;
`
//minutes
const animationMinute = keyframes`
from {
transform: rotate(${props => props.minuteDegrees}deg);
}
to {
transform: rotate(${finalMinuteDegrees}deg);
}
`
const MinuteStyled = styled.div`
animation: ${animationMinute} 4s ease-out infinite;
`
return(
<StyledClock className={index}>
<HourStyled className={hourDegrees} key={index} hourDegrees={hourDegrees}/>
<MinuteStyled className={minuteDegrees} key={index} minuteDegrees={minuteDegrees}/>
</StyledClock>
)
}
Thanks a lot beforehand!
You can create StyledClock or MinuteStyled styled-components outside the target component. Also, you can send props to the styled components if needed.
UPDATED I forked your code below and updated by using a callback function for the keyframes to pass the dynamic degrees codesandbox
const StyledComponent = styled.div`
background: ${props => props.background};
`;
Clock
const StyledClock = styled.div`
width: 6vw;
`;
export default function Clock({ minuteDegrees, hourDegrees, index }) {
return (
<StyledClock className={index}>
<Hours index={index} hourDegrees={hourDegrees} />
<Minutes index={index} minuteDegrees={minuteDegrees} />
</StyledClock>
);
}
Minutes
const animationMinute = keyframes`
from {
transform: rotate(${(props) => props.minuteDegrees}deg);
}
to {
transform: rotate(${(props) => props.finalMinuteDegrees}deg);
}
`;
const MinuteStyled = styled.div`
animation: ${animationMinute} 4s ease-out infinite;
`;
export default function Minutes({ minuteDegrees, index }) {
const finalMinuteDegrees = Number(minuteDegrees + 360);
return (
<MinuteStyled
className={minuteDegrees}
key={index}
minuteDegrees={minuteDegrees}
finalMinuteDegrees={finalMinuteDegrees}
/>
);
}

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;
}

React-select, open sub-menu when hover over an option

I'm trying to build a submenu inside a main menu with React-select, it should be something like this:
When hovering over an option from the main menu, it triggers the submenu to open at the side.
Is there a way to do this using react-select? I couldn't find any example or documentation on this, is there a function like ```optionOnMouseover`` for this? Thank you in advance!
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
...
<Select
value={...}
onChange={...}
options={options}
/>```
This is on click, but if you need on hover,
just modify it
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Select, { components } from "react-select"
const CustomOption = (props) => {
const [submenu, setSubmenu] = useState(false)
const [height, setHeight] = useState(0)
const handleOption = (e) => {
if(submenu) {
setSubmenu(false)
} else {
setHeight(e.clientY)
setSubmenu(true)
}
}
const handleSubOption = (e) => {
console.log('clicked')
}
const { data } = props;
return data.custom ? (
<>
<div onClick={handleOption} className="customs">
{data.label} <span className="caret"/>
{
submenu && (
<div className="dropdown-submenu">
<div className="drops" onClick={handleSubOption}>
Test dropdown 1
</div>
<div className="drops" onClick={handleSubOption}>
Test dropdown 2
</div>
<div className="drops" onClick={handleSubOption}>
Test dropdown 3
</div>
</div>
)
}
</div>
<style jsx>{`
.customs {
height: 36px;
padding: 8px;
position: relative;
}
.drops {
height: 36px;
padding: 8px;
}
.customs:hover, .drops:hover {
background-color: #17cf76;
}
.dropdown-submenu {
position: fixed;
top: ${height - 10}px;
left: 410px;
min-height: 36px;
overflow: auto;
border: 1px solid hsl(0,0%,80%);
border-radius: 4px;
color: #212529;
}
`}</style>
</>
) : (
<components.Option {...props} />
);
};
const options = [
{ custom: true, label: "I'm a custom link", value: "cust" }
];
function App() {
return (
<>
<Select classNamePrefix="category-select" className="w-30" components={{ Option: CustomOption }} options={options} />
<style jsx global>{`
* {
font-family: sans-serif;
text-align: center;
}
.w-30 {
width: 30% !important;
}
`}</style>
</>
)
}
export default App

How to trigger animation using react-transition-group

The problem is I have a form with three states: error, info, and success. Depending on there a response from server am firing toaster using above states I need to add a fade in-out animation when a response from the server is available.
toasterService.js
import React, {useState} from 'react';
import {Transition} from 'react-transition-group';
import './toasterService.css'
export default function ToasterService(content, timeout, style) {
const inProp = useState(true); // always call hook on top level
const duration = timeout;
const transitionStyles = {
entering: {opacity: 1},
entered: {opacity: 1},
exiting: {opacity: 0},
exited: {opacity: 0},
};
let defaultStyle = {};
switch (style) {
case 'info' :
defaultStyle = {
transition: `opacity ${duration}ms ease-in-out`,
opacity: 0,
backgroundColor: '#00c5dc',
color: '#ffffff'
};
break;
case 'success' :
defaultStyle = {
transition: `opacity ${duration}ms ease-in-out`,
opacity: 0,
backgroundColor: '#8ebe4b',
color: '#ffffff'
};
break;
case 'danger' :
defaultStyle = {
transition: `opacity ${duration}ms ease-in-out`,
opacity: 0,
backgroundColor: '#FF0000',
color: '#ffffff'
};
break;
default :
}
return (<div className="main-alert">
<Transition in={inProp} timeout={duration}>
{state => (
<div style={{
...defaultStyle,
...transitionStyles[state]
}}>
{content}
</div>
)}
</Transition>
</div>
);
}
Login.js
import ToastService from '../../services/core/toasterService';
// on click of login btn
socialSignIn = () => {
let obj = {};
obj = this.state;
fetch(url,
{
method: 'post',
body: JSON.stringify(obj)
}).then(function (res) {
console.log(res.json());
ToastService('success', 5000,'info');
return res.json();
})
};
Toast Service receiving 3 arguments but the toaster is not appearing. What I am missing?
I recently built a Toastr component myself, with basic functionality using styled-components, animejs and react-transition-group that might help you to get it right.
Note: I think it's easier to use animejs rather than setting styles for each phase of the transition. You basically get the reference for the entering or exiting element and animate it how you like using animejs.
react-transition-group will give you a reference to the element from these props:
<Transition
key={item.id}
onEntering={animateEnter} // animateEnter will have a reference to the element
onExiting={animateExit} // animateExist will have a reference to the element
timeout={{
enter: 500,
exit: 500
}}
unmountOnExit={true} // I was testing, but I don't think this prop is necessary in my component
>
See working example on CodeSandbox
This is the code:
index.js
import React from "react";
import ReactDOM from "react-dom";
import Toastr from "./Toastr";
import SomeComponent from "./SomeComponent";
import "./styles.css";
function App() {
return (
<Toastr>
<SomeComponent />
</Toastr>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Toastr.js
import React, { useRef, useEffect, useState } from "react";
import styled from "styled-components";
import { TransitionGroup, Transition } from "react-transition-group";
import anime from "animejs";
import ToastrContext from "./ToastrContext";
// CREATE A USE TOASTER HOOK ?
// MAYBE CREATE AN APP STATE TO STORE THE TOASTS
const S = {};
S.FixedContainer = styled.div`
position: fixed;
bottom: 10px;
/* right: 5px; */
/* left: 0; right: 0; */
/* CENTER IT HORIZONTALLY */
left: 50%;
transform: translateX(-50%);
`;
S.ToastContainer = styled.div`
width: 300px;
height: 64px;
margin-top: 10px;
margin-bottom: 10px;
/* padding-left: 10px; */
color: white;
font-weight: bold;
background: #39c16c;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
`;
function Toastr(props) {
const lastToastLengthRef = useRef(0);
const [toasts, setToasts] = useState([]);
const toastID = useRef(0);
console.log("Toastr rendering...");
console.log(toasts);
function addNewToast(toast) {
setToasts(prevState => {
const aux = Array.from(prevState);
aux.push({ msg: toast, id: toastID.current });
toastID.current = toastID.current + 1;
return aux;
});
}
useEffect(() => {
if (toasts.length > lastToastLengthRef.current) {
console.log("useEffect: Toast was added...");
// TOAST WAS ADDED
setTimeout(() => {
setToasts(prevState => {
const aux = Array.from(prevState);
aux.shift();
return aux;
});
}, 1000);
lastToastLengthRef.current = toasts.length;
return;
}
lastToastLengthRef.current = toasts.length;
}, [toasts]);
function animateEnter(element) {
anime({
targets: element,
opacity: 0,
duration: 0
});
anime({
targets: element,
opacity: 1,
easing: "easeOutExpo",
duration: 2000
});
}
function animateExit(element) {
anime({
targets: element,
opacity: 0,
easing: "easeOutExpo",
duration: 2000
});
}
// const toastItems = toasts.map((item,index) =>
// <S.ToastContainer key={item.id}>{item.msg}</S.ToastContainer>
// );
const toastItems = toasts.map((item, index) => (
<Transition
key={item.id}
onEntering={animateEnter}
onExiting={animateExit}
timeout={{
enter: 500,
exit: 500
}}
unmountOnExit={true}
>
<S.ToastContainer>{item.msg}</S.ToastContainer>
</Transition>
));
return (
<React.Fragment>
<S.FixedContainer>
<TransitionGroup component={null}>{toastItems}</TransitionGroup>
{/* {toastItems} */}
</S.FixedContainer>
<ToastrContext.Provider value={addNewToast}>
{props.children}
</ToastrContext.Provider>
</React.Fragment>
);
}
// Toastr.whyDidYouRender = true;
export default Toastr;
ToastrContext.js
import React from "react";
const ToastrContext = React.createContext(null);
export default ToastrContext;
SomeComponent.js (will emit toasts)
import React, { useContext } from "react";
import ToastrContext from "./ToastrContext";
import styled from "styled-components";
function SomeComponent() {
const sendToast = useContext(ToastrContext);
return (
<React.Fragment>
<div>Hey! Click for some toasts!</div>
<button onClick={() => sendToast("This is your toast!")}>Click</button>
</React.Fragment>
);
}
export default SomeComponent;

Resources