Hi there everyone!
Quick Context
I've recently been working on a feature that includes a bit of animation, alongside some design system classes. I wanted to introduce hooks into the code base for the first time, and decided to give it a try!
Challenge
There were two challenges I had when using animations for fading out:
- An element would re-appear after the fadeOut animation without applying a second class to hide it
- When applying a hidden class at the same time as an animation class, the animation didn't happen - just the disappearing :).
Solution
To solve this, I utilized a useEffect() hook which would set the animation class, followed by a setTimeout with a 1-second delay, to first complete the animation and then successfully hide the element we are animating.
I utilized the return function of hooks to clean up any timers on the element to prevent memory leaks.
Below you can see the code I've written (shortened version) to solve the challenge, or you can check out this Code Pen.
if you want to see the issue, comment out the useEffect hook and you will see that it fades out, then comes right back into view!
The isHidden
prop is passed down from a higher component, which changes based on a tap/click.
Code
React
export const SomeNavHeader = ({
title = 'Some Title',
isHidden
planId
}) => {
const TOPBAR_VISIBILITY_CLASSES = {
hidden: 'hide',
visible: ''
}
const ANIMATION_CLASSES = {
fadeIn: 'fade-in',
fadeOut: 'fade-out'
}
// set default state to use fade in and visible class
const [animationClass, setAnimationClass] = useState(ANIMATION_CLASSES.fadeDownAndIn)
const [topbarNavHiddenClass, setTopbarNavHiddenClass] = useState(TOPBAR_VISIBILITY_CLASSES.visible)
// this will run everytime isHidden changes
useEffect(() => {
// set timer ids to null to help with clean up - null is OK here
let hiddenClassTimer = null
if (isHidden) {
// fade out then hide once animation finishes
setAnimationClass(ANIMATION_CLASSES.fadeOut)
hiddenClassTimer = setTimeout(() => {
setTopbarNavHiddenClass(TOPBAR_VISIBILITY_CLASSES.hidden)
}, DELAYS_IN_MS.oneSecond)
} else {
// show topbar and animate it in
setAnimationClass(ANIMATION_CLASSES.fadeIn)
setTopbarNavHiddenClass(TOPBAR_VISIBILITY_CLASSES.visible)
}
// return function helps to clean up timeouts if they are happening when component is removed from dom
return () => {
clearTimeout(hiddenClassTimer)
}
}, [
isHidden
])
return (
<header
className={`some-header-component ${DESIGN_SYS.topBar} ${
DESIGN_SYS.color.neutral90
} ${animationClass} ${topbarNavHiddenClass}`}
>
<p>{title}</p>
</header>
)
}
Scss
.some-header-component {
&.fade-in {
animation-duration: 1s;
animation-name: fadeIn;
}
&.fade-out {
animation-duration: 1s;
animation-name: fadeOut;
}
&.hide {
display: none;
}
}
Is There a Better Way?
I'd absolutely love feedback or other suggestions on the best way to handle the given challenge!
Please let me know if you have any questions!
Top comments (7)
Well done for your first custom hook!
For animating components based on state using CSS, I recommend you to take a look at React Transition Group. It handles the logic for toggling the CSS classes based on the component's state (mount/unmount). If I understand your goal correctly, you could achieve something similar using
enter
&enter-active
classes to show and then hide the component after a delay.As @jdmg94 mentioned, you can also take a look at React Spring. It's an excellent library for animating your components, and I'm a big fan of the hooks API it provides. But, it depends on your needs and if you plan to use it in other places. It introduces a new API, so if you only want to animate this component, getting started with React Transition Group might be a first good step.
Does react transition group work on state changes compared to just mount/unmounting?
It looks like React Spring is a good option - do you know if it's necessary for very easy animations like fading in / fading out and what has your experience been with it?
Thank you so much for commenting and reading through for some suggestions :)
Yes, you can use the
in
prop. For instanceThere is a CodeSandbox in the documentation that can help you to visualize how it works reactcommunity.org/react-transitio...
I don't think it's necessary to bring React Spring for simple animations, especially if it's an isolated use case.
In my experience, managing animations beyond fade in/fade out with different states can be annoying to build with CSS classes compared to an approach like React Spring. (Outside React ecosystem, I would recommend using something like GSAP.)
The principle you will be working with is called a spring, it does not have a defined curve or a set duration. In that it differs greatly from the animation you are probably used to. We think of animation in terms of time and curves, but that in itself causes most of the struggle we face when trying to make elements on the screen move naturally, because nothing in the real world moves like that.
react-spring.io
Hi there,
Thanks for commenting!
Would React Spring be necessary for basic animations or worth including into the bundle? I've heard of it and looks great!
I created a code pen to demo the solution:
codepen.io/avatar-kaleb/pen/voBmzp -- if you want to see the issue, comment out the useEffect hook and you will see that it fades out, then comes right back into view!
Would React Spring help remove this issue without using the hook / setTimeout
Can you post demo of this in codesandbox for example? It would be helpful to see playable example
Here you go!
codepen.io/avatar-kaleb/pen/voBmzp
if you want to see the issue, comment out the useEffect hook and you will see that it fades out, then comes right back into view!
Let me know if you have any questions