React JS: How to animate conditionally rendered components?
Asked Answered
C

2

22

Example is a functional component in which I am rendering a div conditionally. I want this div to fade-in when rendered conditionally and fade-out vice versa.

For that, I have maintained two local state variables: render and fadeIn which are computed based on show prop passed down to the Example component.

What I've done is:

  • When show prop it true, I set render as true, so the div renders conditionally and after a timeout of 10ms I set fadeIn as true which will set CSS classname for my div as show.
  • When show prop it false, I set fadeIn as false, which will set CSS classname for my div as hide and after a timeout of 200ms (transition time in CSS) I set render as false so the div is hidden conditionally.

Code:

interface Props {
  show: boolean;
}

const Example: React.FC<Props> = ({ show, }) => {
  const [render, setRender] = useState(false);
  const [fadeIn, setFadeIn] = useState(false);

  useEffect(() => {
    if (show) {
      // render component conditionally
      setRender(show);

      // change state to for conditional CSS classname which will
      // animate opacity, I had to give a timeout of 10ms else the
      // component shows up abruptly
      setTimeout(() => {
        setFadeIn(show);
      }, 10);
    } else {
      // change state to change component classname for opacity animation
      setFadeIn(false);

      // hide component conditionally after 200 ms
      // because that's the transition time in CSS
      setTimeout(() => {
        setRender(false);
      }, 200);
    }
  }, [
    show,
  ]);

  return (
    <div>
      {render && (
        <div className={`container ${fadeIn ? 'show' : 'hide'}`} />
      )}
    </div>
  );
};

Stylesheet:

.container {
  width: 100px;
  height: 100px;
  background-color: black;
  transition: opacity 0.2s ease;
}

.show {
  opacity: 1;
}

.hide {
  opacity: 0;
}

I believe this is not a good coding practice to achieve the functionality and should maintain only one local state in my component. I need your suggestions on how I can solve this in a better way without using any 3rd Party Library. Thanks :)

Centrifugate answered 25/4, 2020 at 16:32 Comment(2)
You should checkout React Transition Group. It basically solves what you're trying to do with a really clean API. reactcommunity.org/react-transition-groupRedhead
@StephanOlsen thanks, but I don't want to integrate any 3rd party library.Centrifugate
H
4
const [render, setRender] = useState(false);

useEffect(() => {
   if(show) {
     setTimeout(() => {
       setRender(true);
     }, 2000);
   } else {
     setRender(false);
   }
}, [show]);

<div className={cs(s.render, render ? 'show' : undefined)}>
  <p>{content}</p>
</div>

Css:

.render {
  ...,
  visibility: hidden;
  opacity: 0;
  transition: all 0.6s ease;
}

.show {
  visibility: visible;
  opacity: 1;
}

Hope be helpful.

Herbalist answered 25/4, 2020 at 16:57 Comment(8)
Hi, can you please elaborate a little, I'm struggling with understanding cs in <div className={cs(s.render, render ? 'show' : undefined)}>?Centrifugate
cs stands for classnames package in npm,Its a utility for conditionally joining classNames together ** import cs from 'classnames'; **Herbalist
In your example you can just add classes globally and the styles will apply as well. but with this package you can style in your component css or sass file and just effect on that component.Herbalist
Thanks for sharing that but setting visibility to hidden won't really solve my problem. Because the element in still present in DOM, which is quite unnecessary for my use case.Centrifugate
Now I got you, let me try and testHerbalist
refer to this question: #40064749Herbalist
actually, it is not a conditional rendering, it is a conditional visibilityHebron
@MichaelYurin ya thats why I shared a linkHerbalist
H
3

Actually there is a HTML attribute for this situationes called onAnimationEnd or onAnimationStart based on your case.

Example:

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>
  );
}

More info can be found in this article

Harmony answered 15/5, 2023 at 9:47 Comment(2)
Very nice! Never knew about that oneThusly
Nice implementation...!Waechter

© 2022 - 2024 — McMap. All rights reserved.