How to use Conditional Rendering with Animation in React
oussel
Posted on February 12, 2021
In this article we will code together an example of conditional rendering in React with a FadeIn/FadeOut effect.
Ofc you can use libraries like ReactCSSTransitionGroup to achieve that but if you just need to code a simple animation with conditional Rendering without using any 3rd Party Library then you are in the right place 😊
What is the problem :
To understand what I’m trying to explain here , we should first take a look on the importance of conditional rendering in react / and what are different approaches to animate (FadeIn/Fadeout) mount/Unmout your elements in your page.
Without Conditional Rendering :
With just HTML/CSS and the some help of JS (No conditional Rendering) we can achieve that by modifying opacity gradually with keyframes and animation CSS property or just transition CSS property:
import { useState } from "react";
import "./styles.css";
const mountedStyle = { animation: "inAnimation 250ms ease-in" };
const unmountedStyle = {
animation: "outAnimation 270ms ease-out",
animationFillMode: "forwards"
};
export default function App() {
const [showDiv, setShowDiv] = useState(false);
return (
<div className="App">
<button onClick={() => setShowDiv(!showDiv)}>Show/Hide</button>
<div
className="transitionDiv"
style={showDiv ? mountedStyle : unmountedStyle}
></div>
</div>
);
}
Style.css
.transitionDiv {
background: red;
height: 100px;
width: 200px;
margin: 0 auto;
}
@keyframes inAnimation {
0% {
opacity: 0;
visibility: hidden;
}
100% {
opacity: 1;
visibility: visible;
}
}
@keyframes outAnimation {
0% {
opacity: 1;
}
100% {
opacity: 0;
visibility: hidden;
}
}
Demo :
As you can see based on ShowDiv value we can set Mount/UnMount Style and start a transition to affect
property opacity that can has a numerical value setting between (0-1), so keyframes can be calculated across the duration provided in transition.
PS : You can only transition on ordinal/calculable properties (an easy way of thinking of this is any property with a numeric start and end number value..though there are a few exceptions).
So in our case visibility property that has a binary setting (visible/hidden) behaves differently ; so once the transition duration elapses, the property simply switches state, you see this as a delay- but it can actually be seen as the final keyframe of the transition animation, with the intermediary keyframes not having been calculated
What React gives us different in conditional rendering?
In the approach above, we are using transition on visibility property this property tells the browser whether to show an element or not BUT the browser still treats it as an element that takes his own space and affect the flow of other elements in page.
And even if we found a trick like to set property height: 0 or display: none when transition ends, this will ofc hide and display:none will even remove it from the document layout BUT the element will remain in the DOM Object.
In React, conditional rendering works similar to JavaScript conditions. And based on if/else statement React will deals with DOM Object efficiently and he will decide to Hide or show elements. therefore, with conditional rendering in react, your DOM object will not contain unnecessary and unwanted elements, this will improve the smooth running of your code and debugging as well as design.
BUTTTTTT … How can you benefit from the efficiency of Conditional Rendering in React and in the same time apply transition or animation on something that doesn’t exist in your DOM Object when React decide to hide your element :) ? Now you can see all the problem in front of you.
If you try to add a simple conditional rendering in our exemple above :
//...
export default function App() {
const [showDiv, setShowDiv] = useState(false);
return (
<div className="App">
<button onClick={() => setShowDiv(!showDiv)}>Show/Hide</button>
//Conditional Rendering
{ showDiv &&
<div
className="transitionDiv"
style={showDiv ? mountedStyle : unmountedStyle}
></div>
}
</div>
);
}
You will notice that our Div lose animation when you try to hide it , that's because With conditional Rendering ,React will remove your div from the DOM Object before transition starts .
To solve this problem, we can use the following two methods :
- Using OnAnimationEnd Event :
React offers some built-in events handlers for catching some events and handle them in the capture phase. React components can use native events like preventDefault() and stopPropagation() ...
In our case we are interested to catch animation transition when we want to Unmount our component and to have more control during this phase . this can be done with :
onAnimationEnd — This event triggers the moment the animation ended.
Let's code it :
//...
export default function App() {
const [isMounted, setIsMounted] = useState(false);
const [showDiv, setShowDiv] = useState(false);
return (
<div className="App">
<button onClick={() => {
setIsMounted(!isMounted)
if (!showDiv) setShowDiv(true); //We should Render our Div
}
}>Show/Hide</button>
{ //Conditional Rendering
showDiv && <div
className="transitionDiv"
style={isMounted ? mountedStyle : unmountedStyle}
onAnimationEnd={() => { if (!isMounted) setShowDiv(false) }}
></div>}
</div>
);
}
As you can see , Show Div that control the rendering will wait until Anitmation Ends then it will switch to false . Thanks to onAnimationEnd Event trigger.
We introduce also a new variable isMounted that will indicate if our user click button (Show/Hide) .
Demo :
- Coding a simple Custom hook :
to solve this we can code a simple function that will delay the unmount stage and let transition to finish before React remove our element from DOM Object.
this function will give to our variable showDiv a delay time before switching its value to false .
import React, { useEffect, useState } from "react";
function useDelayUnmount(isMounted, delayTime) {
const [showDiv, setShowDiv] = useState(false);
useEffect(() => {
let timeoutId;
if (isMounted && !showDiv) {
setShowDiv(true);
} else if (!isMounted && showDiv) {
timeoutId = setTimeout(() => setShowDiv(false), delayTime); //delay our unmount
}
return () => clearTimeout(timeoutId); // cleanup mechanism for effects , the use of setTimeout generate a sideEffect
}, [isMounted, delayTime, showDiv]);
return showDiv;
}
We introduce a new variable isMounted that will indicate if our user click button (Show/Hide) , based on that useDelayUnmount will give ShowDiv a certain time before switching its value , with that our animation will affect Div element before remove it from DOM.
FullVersion :
import React, { useEffect, useState } from "react";
function useDelayUnmount(isMounted, delayTime) {
const [showDiv, setShowDiv] = useState(false);
useEffect(() => {
let timeoutId;
if (isMounted && !showDiv) {
setShowDiv(true);
} else if (!isMounted && showDiv) {
timeoutId = setTimeout(() => setShowDiv(false), delayTime); //delay our unmount
}
return () => clearTimeout(timeoutId); // cleanup mechanism for effects , the use of setTimeout generate a sideEffect
}, [isMounted, delayTime, showDiv]);
return showDiv;
}
const mountedStyle = { animation: "inAnimation 250ms ease-in" };
const unmountedStyle = {
animation: "outAnimation 270ms ease-out",
animationFillMode: "forwards"
};
export default function App() {
const [isMounted, setIsMounted] = useState(false);
const showDiv = useDelayUnmount(isMounted,250);
return (
<div className="App">
<button onClick={() => setIsMounted(!isMounted)}>Show/Hide</button>
//Conditional Rendering
{ showDiv &&
<div
className="transitionDiv"
style={isMounted ? mountedStyle : unmountedStyle}
></div>
}
</div>
);
}
Demo :
That's all , Hope you learned something new :)
Posted on February 12, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.