Animation Classes & useEffect Hooks - Is There a Better Way?

Kaleb M - Jul 16 '19 - - Dev Community

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:

  1. An element would re-appear after the fadeOut animation without applying a second class to hide it
  2. 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!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player