Super easy react mount/unmount animations with hooks
July 05, 20198 minute read ⏱
One of the main use cases for animations on the web is simply adding and removing elements from the page. However, doing that in react can be a pain in the ass because we cannot directly manipulate the DOM elements! Since we let react take care of rendering, we are forced to do animations the react-way. When faced with this revelation, some developers begin to miss the olden days of jQuery where you could just do:
$("#my-element").fadeIn("slow");
In case you are wondering what that the difficulty is exactly, let me illustrate with a quick example:
/* styles.css */
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
// index.js
const App = ({ show = true }) => (
show
? <div style={{ animation: `fadeIn 1s` }}>HELLO</div>
: null
)
This is all we need to animate mounting of the component with a fadeIn
, but there is no way to animate the unmounting, because we remove the the <div/>
from the DOM as soon as the show
prop changes to false! The component is gone and there is simply no way animate it anymore. What can we do about it? 🤔
Basically, we need to tell react to:
- When the
show
prop changes, don’t unmount just yet, but “schedule” an unmount. - Start the unmount animation.
- As soon as the animation finishes, unmount the component.
I want to show you the simplest way to accomplish this using pure CSS and hooks. Of course, for more advanced use cases there are excellent libraries like react-spring.
For the impatient, here’s the code, divided into 3 files:
// index.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import Fade from "./Fade";
const App = () => {
const [show, setShow] = useState(false);
return (
<div>
<button onClick={() => setShow(show => !show)}>
{show ? "hide" : "show"}
</button>
<Fade show={show}>
<div> HELLO </div>
</Fade>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
// Fade.js
import React, { useEffect, useState } from "react";
const Fade = ({ show, children }) => {
const [shouldRender, setRender] = useState(show);
useEffect(() => {
if (show) setRender(true);
}, [show]);
const onAnimationEnd = () => {
if (!show) setRender(false);
};
return (
shouldRender && (
<div
style={{ animation: `${show ? "fadeIn" : "fadeOut"} 1s` }}
onAnimationEnd={onAnimationEnd}
>
{children}
</div>
)
);
};
export default Fade;
/* styles.css */
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
Let’s break down what’s going on here, starting with the first file. The interesting part is this:
// index.js
const App = () => {
const [show, setShow] = useState(false);
return (
<div>
<button onClick={() => setShow(show => !show)}>
{show ? "hide" : "show"}
</button>
<Fade show={show}> <div> HELLO </div> </Fade> </div>
);
};
We simply pass a show
prop which controls whether to show the children of the <Fade />
component. The rest of the code in this component is just managing the hiding/showing using the useState hook.
<Fade/>
component receives 2 props: show
and children
. We use the value of the show
prop to initialize the shouldRender
state of the <Fade />
component:
// Fade.js
const Fade = ({ show, children }) => {
const [shouldRender, setRender] = useState(show);
// ...
}
This gives use a way to separate the animation from the mounting/unmounting.
The show
prop controls whether we apply the fadeIn
or fadeOut
animation and the shouldRender
state controls the mounting/unmounting:
// ...
return (
shouldRender && ( <div
style={{ animation: `${show ? "fadeIn" : "fadeOut"} 1s` }} onAnimationEnd={onAnimationEnd}
>
{children}
</div>
)
);
// ...
You can recall from before that our main problem was that react will unmount the component at the same time as we try to apply the animation, which results in the component disappearing immediately. But now we have separated those two steps!
We just need a way to tell react to sequence the fadeOut
animation and the unmounting and we’re done! 💪
For this, we can use the onAnimationEnd event. When the animation has ended running and the component should be hidden (show === false
) then set the shouldRender
to false!
const onAnimationEnd = () => {
if (!show) setRender(false);
};
The whole example is also on Codesandbox where you can play around with it!
Hey! 👋 Before you go! 🏃♂️
If you enjoyed this post, you can follow me on twitter for more programming content or drop me an email 🙂
I absolutely love comments and feedback!!! ✌️