score:124

Accepted answer

This is a bit lengthy but I've used all the native events and methods to achieve this animation. No ReactCSSTransitionGroup, ReactTransitionGroup and etc.

Things I've used

  • React lifecycle methods
  • onTransitionEnd event

How this works

  • Mount the element based on the mount prop passed(mounted) and with default style(opacity: 0)
  • After mount or update, use componentDidMount (componentWillReceiveProps for further updates)to change the style (opacity: 1) with a timeout(to make it async).
  • During unmount, pass a prop to the component to identify unmount, change the style again(opacity: 0), onTransitionEnd, remove unmount the element from the DOM.

Continue the cycle.

Go through the code, you'll understand. If any clarification is needed, please leave a comment.

Hope this helps.

class App extends React.Component{
  constructor(props) {
    super(props)
    this.transitionEnd = this.transitionEnd.bind(this)
    this.mountStyle = this.mountStyle.bind(this)
    this.unMountStyle = this.unMountStyle.bind(this)
    this.state ={ //base css
      show: true,
      style :{
        fontSize: 60,
        opacity: 0,
        transition: 'all 2s ease',
      }
    }
  }
  
  componentWillReceiveProps(newProps) { // check for the mounted props
    if(!newProps.mounted)
      return this.unMountStyle() // call outro animation when mounted prop is false
    this.setState({ // remount the node when the mounted prop is true
      show: true
    })
    setTimeout(this.mountStyle, 10) // call the into animation
  }
  
  unMountStyle() { // css for unmount animation
    this.setState({
      style: {
        fontSize: 60,
        opacity: 0,
        transition: 'all 1s ease',
      }
    })
  }
  
  mountStyle() { // css for mount animation
    this.setState({
      style: {
        fontSize: 60,
        opacity: 1,
        transition: 'all 1s ease',
      }
    })
  }
  
  componentDidMount(){
    setTimeout(this.mountStyle, 10) // call the into animation
  }
  
  transitionEnd(){
    if(!this.props.mounted){ // remove the node on transition end when the mounted prop is false
      this.setState({
        show: false
      })
    }
  }
  
  render() {
    return this.state.show && <h1 style={this.state.style} onTransitionEnd={this.transitionEnd}>Hello</h1> 
  }
}

class Parent extends React.Component{
  constructor(props){
    super(props)
    this.buttonClick = this.buttonClick.bind(this)
    this.state = {
      showChild: true,
    }
  }
  buttonClick(){
    this.setState({
      showChild: !this.state.showChild
    })
  }
  render(){
    return <div>
        <App onTransitionEnd={this.transitionEnd} mounted={this.state.showChild}/>
        <button onClick={this.buttonClick}>{this.state.showChild ? 'Unmount': 'Mount'}</button>
      </div>
  }
}

ReactDOM.render(<Parent />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-with-addons.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

score:0

Here my 2cents: thanks to @deckele for his solution. My solution is based on his, it's the stateful's component version, fully reusable.

here my sandbox: https://codesandbox.io/s/302mkm1m.

here my snippet.js:

import ReactDOM from "react-dom";
import React, { Component } from "react";
import style from  "./styles.css"; 

class Tooltip extends Component {

  state = {
    shouldRender: false,
    isMounted: true,
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.state.shouldRender !== nextState.shouldRender) {
      return true
    }
    else if (this.state.isMounted !== nextState.isMounted) {
      console.log("ismounted!")
      return true
    }
    return false
  }
  displayTooltip = () => {
    var timeoutId;
    if (this.state.isMounted && !this.state.shouldRender) {
      this.setState({ shouldRender: true });
    } else if (!this.state.isMounted && this.state.shouldRender) {
      timeoutId = setTimeout(() => this.setState({ shouldRender: false }), 500);
      () => clearTimeout(timeoutId)
    }
    return;
  }
  mountedStyle = { animation: "inAnimation 500ms ease-in" };
  unmountedStyle = { animation: "outAnimation 510ms ease-in" };

  handleToggleClicked = () => {
    console.log("in handleToggleClicked")
    this.setState((currentState) => ({
      isMounted: !currentState.isMounted
    }), this.displayTooltip());
  };

  render() {
    var { children } = this.props
    return (
      <main>
        {this.state.shouldRender && (
          <div className={style.tooltip_wrapper} >
            <h1 style={!(this.state.isMounted) ? this.mountedStyle : this.unmountedStyle}>{children}</h1>
          </div>
        )}

        <style>{`

           @keyframes inAnimation {
    0% {
      transform: scale(0.1);
      opacity: 0;
    }
    60% {
      transform: scale(1.2);
      opacity: 1;
    }
    100% {
      transform: scale(1);  
    }
  }

  @keyframes outAnimation {
    20% {
      transform: scale(1.2);
    }
    100% {
      transform: scale(0);
      opacity: 0;
    }
  }
          `}
        </style>
      </main>
    );
  }
}


class App extends Component{

  render(){
  return (
    <div className="App"> 
      <button onClick={() => this.refs.tooltipWrapper.handleToggleClicked()}>
        click here </button>
      <Tooltip
        ref="tooltipWrapper"
      >
        Here a children
      </Tooltip>
    </div>
  )};
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

score:0

Here's how I solved this in 2019, while making a loading spinner. I'm using React functional components.

I have a parent App component that has a child Spinner component.

App has state for whether the app is loading or not. When the app is loading, Spinner is rendered normally. When the app is not loading (isLoading is false) Spinner is rendered with the prop shouldUnmount.

App.js:

import React, {useState} from 'react';
import Spinner from './Spinner';

const App = function() {
    const [isLoading, setIsLoading] = useState(false);

    return (
        <div className='App'>
            {isLoading ? <Spinner /> : <Spinner shouldUnmount />}
        </div>
    );
};

export default App;

Spinner has state for whether it's hidden or not. In the beginning, with default props and state, Spinner is rendered normally. The Spinner-fadeIn class animates it fading in. When Spinner receives the prop shouldUnmount it renders with the Spinner-fadeOut class instead, animating it fading out.

However I also wanted the component to unmount after fading out.

At this point I tried using the onAnimationEnd React synthetic event, similar to @pranesh-ravi's solution above, but it didn't work. Instead I used setTimeout to set the state to hidden with a delay the same length as the animation. Spinner will update after the delay with isHidden === true, and nothing will be rendered.

The key here is that the parent doesn't unmount the child, it tells the child when to unmount, and the child unmounts itself after it takes care of its unmounting business.

Spinner.js:

import React, {useState} from 'react';
import './Spinner.css';

const Spinner = function(props) {
    const [isHidden, setIsHidden] = useState(false);

    if(isHidden) {
        return null

    } else if(props.shouldUnmount) {
        setTimeout(setIsHidden, 500, true);
        return (
            <div className='Spinner Spinner-fadeOut' />
        );

    } else {
        return (
            <div className='Spinner Spinner-fadeIn' />
        );
    }
};

export default Spinner;

Spinner.css:

.Spinner {
    position: fixed;
    display: block;
    z-index: 999;
    top: 50%;
    left: 50%;
    margin: -40px 0 0 -20px;
    height: 40px;
    width: 40px;
    border: 5px solid #00000080;
    border-left-color: #bbbbbbbb;
    border-radius: 40px;
}

.Spinner-fadeIn {
    animation: 
        rotate 1s linear infinite,
        fadeIn .5s linear forwards;
}

.Spinner-fadeOut {
    animation: 
        rotate 1s linear infinite,
        fadeOut .5s linear forwards;
}

@keyframes fadeIn {
    0% {
        opacity: 0;
    }
    100% {
        opacity: 1;
    }
}
@keyframes fadeOut {
    0% {
        opacity: 1;
    }
    100% {
        opacity: 0;
    }
}

@keyframes rotate {
    100% {
        transform: rotate(360deg);
    }
}

score:0

I was also in dire need of single component Animation . I tired using React Motion but i was pulling my hairs for such a trivial issue.. (i thing). After some googling i came across this post on their git repo . Hope it helps someone..

Referenced From & also the credit. This works for me as of now. My use case was a modal to animate and unmount in case of load and unload.

class Example extends React.Component {
  constructor() {
    super();
    
    this.toggle = this.toggle.bind(this);
    this.onRest = this.onRest.bind(this);

    this.state = {
      open: true,
      animating: false,
    };
  }
  
  toggle() {
    this.setState({
      open: !this.state.open,
      animating: true,
    });
  }
  
  onRest() {
    this.setState({ animating: false });
  }
  
  render() {
    const { open, animating } = this.state;
    
    return (
      <div>
        <button onClick={this.toggle}>
          Toggle
        </button>
        
        {(open || animating) && (
          <Motion
            defaultStyle={open ? { opacity: 0 } : { opacity: 1 }}
            style={open ? { opacity: spring(1) } : { opacity: spring(0) }}
            onRest={this.onRest}
          >
            {(style => (
              <div className="box" style={style} />
            ))}
          </Motion>
        )}
      </div>
    );
  }
}

score:0

This can be done easily using the CSSTransition component from react-transition-group, which is just like the libraries you mentioned. The trick is you need to wrap the CSSTransition component without a show/hide mechanism like you typically would.i.e. {show && <Child>}... Otherwise you are hiding the animation and it won't work. Example:

ParentComponent.js

import React from 'react';
import {CSSTransition} from 'react-transition-group';

function ParentComponent({show}) {
return (
  <CSSTransition classes="parentComponent-child" in={show} timeout={700}>
    <ChildComponent>
  </CSSTransition>
)}


ParentComponent.css

// animate in
.parentComponent-child-enter {
  opacity: 0;
}
.parentComponent-child-enter-active {
  opacity: 1;
  transition: opacity 700ms ease-in;
}
// animate out
.parentComponent-child-exit {
  opacity: 1;
}
.parentComponent-child-exit-active {
  opacity: 0;
  transition: opacity 700ms ease-in;
}

score:0

I know there are a lot of answers here, but I still did not find one that suits my needs. I want:

  • Functional components
  • A solution that'll allow my components to easily fade in/out when they're mounted/unmounted.

After many hours of fiddling, I have a solution that works I'd say 90%. I've written the limitation in a comment block in the code below. I'd still love a better solution, but this is the best I've found, including the other solutions here.

const TIMEOUT_DURATION = 80 // Just looked like best balance of silky smooth and stop delaying me.

// Wrap this around any views and they'll fade in and out when mounting /
// unmounting.  I tried using <ReactCSSTransitionGroup> and <Transition> but I
// could not get them to work.  There is one major limitation to this approach:
// If a component that's mounted inside of <Fade> has direct prop changes,
// <Fade> will think that it's a new component and unmount/mount it.  This
// means the inner component will fade out and fade in, and things like cursor
// position in forms will be reset. The solution to this is to abstract <Fade>
// into a wrapper component.

const Fade: React.FC<{}> = ({ children }) => {
  const [ className, setClassName ] = useState('fade')
  const [ newChildren, setNewChildren ] = useState(children)

  const effectDependency = Array.isArray(children) ? children : [children]

  useEffect(() => {
    setClassName('fade')

    const timerId = setTimeout(() => {
      setClassName('fade show')
      setNewChildren(children)
    }, TIMEOUT_DURATION)

    return () => {
      clearTimeout(timerId)
    }   

  }, effectDependency)

  return <Container fluid className={className + ' p-0'}>{newChildren}</Container>
}

If you have a component you want to fade in/out, wrap it in <Fade> Ex. <Fade><MyComponent/><Fade>.

Note that this uses react-bootstrap for the class names and for <Container/>, but both could be easily replaced with custom CSS and a regular old <div>.

score:0

If I use Velocity or AnimeJS library to animate node directly (instead of css or setTimeout), then I found out I can design a hook to provide the animation status on and function onToggle to kick off the animation (ex. slidedown, fade).

Basically what the hook does is to toggle on and off the animation, and afterwards update the on accordingly. Therefore we can get the status of the animation accurately. Without doing so would reply on a ad-hoc duration.

/**
 * A hook to provide animation status.
 * @class useAnimate
 * @param {object} _                props
 * @param {async} _.animate         Promise to perform animation
 * @param {object} _.node           Dom node to animate
 * @param {bool} _.disabled         Disable animation
 * @returns {useAnimateObject}      Animate status object
 * @example
 *   const { on, onToggle } = useAnimate({
 *    animate: async () => { },
 *    node: node
 *  })
 */

import { useState, useCallback } from 'react'

const useAnimate = ({
  animate, node, disabled,
}) => {
  const [on, setOn] = useState(false)

  const onToggle = useCallback(v => {
    if (disabled) return
    if (v) setOn(true)
    animate({ node, on: v }).finally(() => {
      if (!v) setOn(false)
    })
  }, [animate, node, disabled, effect])

  return [on, onToggle]
}

export default useAnimate

The usage is the following,

  const ref = useRef()
  const [on, onToggle] = useAnimate({
    animate: animateFunc,
    node: ref.current,
    disabled
  })
  const onClick = () => { onToggle(!on) }

  return (
      <div ref={ref}>
          {on && <YOUROWNCOMPONENT onClick={onClick} /> }
      </div>
  )

and the animate implementation could be,

import anime from 'animejs'

const animateFunc = (params) => {
  const { node, on } = params
  const height = on ? 233 : 0
  return new Promise(resolve => {
    anime({
      targets: node,
      height,
      complete: () => { resolve() }
    }).play()
  })
}

score:0

You can use React SyntheticEvent for that.

With events like onAnimationEnd or onTransitionEnd you can accomplish that.

React Docs: https://reactjs.org/docs/events.html#animation-events

Code Example: https://dev.to/michalczaplinski/super-easy-react-mount-unmount-animations-with-hooks-4foj

score:0

You could always use React lifecycle methods but react-transition-group is by far the most convenient library for animations I have come across, Whether you are using styled-components or plain css. It is especially useful when you want to track the mounting and unmounting of your component and render animations accordingly. Use Transition with styled-components and CSSTransition when you are using plain css classnames.

score:0

If you are looking for simple hooks example:

import React, { useEffect, useReducer } from "react";
import ReactDOM from "react-dom";

const ANIMATION_TIME = 2 * 1000;

function Component() {
  const [isMounted, toggleMounted] = useReducer((p) => !p, true);
  const [isAnimateAnmount, toggleAnimateUnmount] = useReducer((p) => !p, false);
  const [isVisible, toggleVisible] = useReducer((p) => (p ? 0 : 1), 0);

  useEffect(() => {
    if (isAnimateAnmount) {
      toggleVisible();
      toggleAnimateUnmount();
      setTimeout(() => {
        toggleMounted();
      }, ANIMATION_TIME);
    }
  }, [isAnimateAnmount]);

  useEffect(() => {
    toggleVisible();
  }, [isMounted]);

  return (
    <>
      <button onClick={toggleAnimateUnmount}>toggle</button>
      <div>{isMounted ? "Mounted" : "Unmounted"}</div>
      {isMounted && (
        <div
          style={{
            fontSize: 60,
            opacity: isVisible,
            transition: "all 2s ease"
          }}
        >
          Example
        </div>
      )}
    </>
  );
}

Edit Animation with unmount

score:0

I created a general purpose WrapperComponent called MountAnimation so that you can animate elements in and out without always having to write the same thing over and over. It uses CSSTransitions under the hood so you need to install that.

  1. Install dependecies
npm install react-transition-group
  1. Create component in one of your folders
import { CSSTransition } from "react-transition-group"

export const MountAnimation = ({
  children,
  timeout = 300, // MATCH YOUR DEFAULT ANIMATION DURATION
  isVisible = false,
  unmountOnExit = true,
  classNames = "transition-translate-y", // ADD YOUR DEFAULT ANIMATION
  ...restProps
}) => {
  return (
    <CSSTransition
      in={isVisible}
      timeout={timeout}
      classNames={classNames}
      unmountOnExit={unmountOnExit}
      {...restProps}
    >
      <div>{children}</div>
    </CSSTransition>
  )
}
  1. Simply use it like this:
import { MountAnimation } from '../../path/to/component'

...

const [isElementVisible, setIsElementVisible] = useState(false)

return (
    <MountAnimation isVisible={isElementVisible}>
       // your content here
    </MountAnimation>

)
  1. (Go creative here) You need to declare your animation in your CSS file. Make sure you declare this in a globally available CSS file if you are code-splitting. In this example I am using the following animation:
.transition-translate-y-enter {
  opacity: 0;
  transform: translateY(-5px);
}
.transition-translate-y-enter-active {
  opacity: 1;
  transform: translateY(0px);
  transition: opacity 300ms ease-in-out, transform 300ms ease-in-out;
}
.transition-translate-y-exit {
  opacity: 1;
  transform: translateY(0px);
}
.transition-translate-y-exit-active {
  opacity: 0;
  transform: translateY(-5px);
  transition: opacity 300ms ease-in-out, transform 300ms ease-in-out;
}

Here is a live example of this implementation:

https://codesandbox.io/s/vibrant-elion-ngfzr?file=/src/App.js

score:1

Animating enter and exit transitions is much easier with react-move.

example on codesandbox

score:2

For those considering react-motion, animating a single component when it mounts and unmounts can be overwhelming to set up.

There's a library called react-motion-ui-pack that makes this process a lot easier to start with. It's a wrapper around react-motion, which means you get all the benefits from the library (i.e. you are able to interrupt the animation, have multiple unmounts happen at the same time).

Usage:

import Transition from 'react-motion-ui-pack'

<Transition
  enter={{ opacity: 1, translateX: 0 }}
  leave={{ opacity: 0, translateX: -100 }}
  component={false}
>
  { this.state.show &&
      <div key="hello">
        Hello
      </div>
  }
</Transition>

Enter defines what the end state of the component should be; leave is the style that is applied when the component is unmounted.

You might find that once you have used the UI pack a couple of times, the react-motion library might not be as daunting anymore.

score:2

You can do this with React Transition Group. It gives you CSS classes, so you can write your animation code in those CSS classes.

Follow this simple example

import {CSSTransition } from 'react-transition-group';//This should be imported
import './AnimatedText.css';

const AnimatedText = () => {
    const [showText, setShowText] = useState(false); //By default text will be not shown

    //Handler to switch states
    const switchHandler = () =>{
        setShowText(!showText);
    };

    return (
        //in : pass your state here, it will used by library to toggle. It should be boolean
        //timeout: your amination total time(it should be same as mentioned in css)
        //classNames: give class name of your choice, library will prefix it with it's animation classes
        //unmountOnExit: Component will be unmounted when your state changes to false
        <CSSTransition in={showText} timeout={500} classNames='fade' unmountOnExit={true}>
            <h1>Animated Text</h1>
        </CSSTransition>  
        <button onClick={switchHandler}>Show Text</button>                  
    );
};

export default AnimatedText;

Now, let's write the animation in CSS file(AnimatedText.css), Remember classNames property(in this case fade)

//fade class should be prefixed

/*****Fade In effect when component is mounted*****/
//This is when your animation starts
fade-enter {
  opacity: 0;
}

//When your animation is active
.fade-enter.fade-enter-active {
  opacity: 1;
  transition: all 500ms ease-in;
}
/*****Fade In effect when component is mounted*****/


/*****Fade Out effect when component is unmounted*****/
.fade-exit {
  opacity: 1;
}
.fade-exit-active {
  opacity: 0;
  transition: all 500ms ease-out;
}

/*****Fade Out effect when component is unmounted*****/

There's also a appear class, which can be used when your component loads for the first time. Check documentation for more details

score:8

I think using Transition from react-transition-group is probably the easiest way to track mounting/unmounting. It is incredibly flexible. I'm using some classes to show how easy it is to use but you can definitely hook up your own JS animations utilizing addEndListener prop - which I've had a lot of luck using GSAP with as well.

Sandbox: https://codesandbox.io/s/k9xl9mkx2o

And here's my code.

import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Transition } from "react-transition-group";
import styled from "styled-components";

const H1 = styled.h1`
  transition: 0.2s;
  /* Hidden init state */
  opacity: 0;
  transform: translateY(-10px);
  &.enter,
  &.entered {
    /* Animate in state */
    opacity: 1;
    transform: translateY(0px);
  }
  &.exit,
  &.exited {
    /* Animate out state */
    opacity: 0;
    transform: translateY(-10px);
  }
`;

const App = () => {
  const [show, changeShow] = useState(false);
  const onClick = () => {
    changeShow(prev => {
      return !prev;
    });
  };
  return (
    <div>
      <button onClick={onClick}>{show ? "Hide" : "Show"}</button>
      <Transition mountOnEnter unmountOnExit timeout={200} in={show}>
        {state => {
          let className = state;
          return <H1 className={className}>Animate me</H1>;
        }}
      </Transition>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

score:9

Framer motion

Install framer-motion from npm.

import { motion, AnimatePresence } from "framer-motion"

export const MyComponent = ({ isVisible }) => (
  <AnimatePresence>
    {isVisible && (
      <motion.div
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
      />
    )}
  </AnimatePresence>
)

score:14

I countered this problem during my work, and simple as it seemed, it is really not in React. In a normal scenario where you render something like:

this.state.show ? {childen} : null;

as this.state.show changes the children are mounted/unmounted right away.

One approach I took is creating a wrapper component Animate and use it like

<Animate show={this.state.show}>
  {childen}
</Animate>

now as this.state.show changes, we can perceive prop changes with getDerivedStateFromProps(componentWillReceiveProps) and create intermediate render stages to perform animations.

A stage cycle might look like this

We start with Static Stage when the children is mounted or unmounted.

Once we detect the show flag changes, we enter Prep Stage where we calculate necessary properties like height and width from ReactDOM.findDOMNode.getBoundingClientRect().

Then entering Animate State we can use css transition to change height, width and opacity from 0 to the calculated values (or to 0 if unmounting).

At the end of transition, we use onTransitionEnd api to change back to Static stage.

There are much more details to how the stages transfer smoothly but this could be overall idea:)

If anyone interested, I created a React library https://github.com/MingruiZhang/react-animate-mount to share my solution. Any feedback welcome:)

score:16

Using the knowledge gained from Pranesh's answer, I came up with an alternate solution that's configurable and reusable:

const AnimatedMount = ({ unmountedStyle, mountedStyle }) => {
  return (Wrapped) => class extends Component {
    constructor(props) {
      super(props);
      this.state = {
        style: unmountedStyle,
      };
    }

    componentWillEnter(callback) {
      this.onTransitionEnd = callback;
      setTimeout(() => {
        this.setState({
          style: mountedStyle,
        });
      }, 20);
    }

    componentWillLeave(callback) {
      this.onTransitionEnd = callback;
      this.setState({
        style: unmountedStyle,
      });
    }

    render() {
      return <div
        style={this.state.style}
        onTransitionEnd={this.onTransitionEnd}
      >
        <Wrapped { ...this.props } />
      </div>
    }
  }
};

Usage:

import React, { PureComponent } from 'react';

class Thing extends PureComponent {
  render() {
    return <div>
      Test!
    </div>
  }
}

export default AnimatedMount({
  unmountedStyle: {
    opacity: 0,
    transform: 'translate3d(-100px, 0, 0)',
    transition: 'opacity 250ms ease-out, transform 250ms ease-out',
  },
  mountedStyle: {
    opacity: 1,
    transform: 'translate3d(0, 0, 0)',
    transition: 'opacity 1.5s ease-out, transform 1.5s ease-out',
  },
})(Thing);

And finally, in another component's render method:

return <div>
  <ReactTransitionGroup>
    <Thing />
  </ReactTransitionGroup>
</div>

score:42

Here is my solution using the new hooks API (with TypeScript), based on this post, for delaying the component's unmount phase:

function useDelayUnmount(isMounted: boolean, delayTime: number) {
    const [ shouldRender, setShouldRender ] = useState(false);

    useEffect(() => {
        let timeoutId: number;
        if (isMounted && !shouldRender) {
            setShouldRender(true);
        }
        else if(!isMounted && shouldRender) {
            timeoutId = setTimeout(
                () => setShouldRender(false), 
                delayTime
            );
        }
        return () => clearTimeout(timeoutId);
    }, [isMounted, delayTime, shouldRender]);
    return shouldRender;
}

Usage:

const Parent: React.FC = () => {
    const [ isMounted, setIsMounted ] = useState(true);
    const shouldRenderChild = useDelayUnmount(isMounted, 500);
    const mountedStyle = {opacity: 1, transition: "opacity 500ms ease-in"};
    const unmountedStyle = {opacity: 0, transition: "opacity 500ms ease-in"};

    const handleToggleClicked = () => {
        setIsMounted(!isMounted);
    }

    return (
        <>
            {shouldRenderChild && 
                <Child style={isMounted ? mountedStyle : unmountedStyle} />}
            <button onClick={handleToggleClicked}>Click me!</button>
        </>
    );
}

CodeSandbox link.


Related Query

More Query from same tag