Animating Modal Appear/Hide

Viewed 595

I have a modal that is displayed when the props.hidden property is set to true and shown when it isn't.

Because I want the show/hide state to animate I need to first change the opacity of the modal to 0, then turn off the hidden css property and finally change the opacity to 100.

Likewise, when hiding the model, I need to change the opacity back to 0 first, and only then can I assign the hidden css property.

To take care of this I'm using two state vars props.hidden and actualHidden. props.hidden refers to the target state of the modal of whether it should be hidden or not (after transition) whereas actualHidden captures whether the CSS hidden property should be applied.

I have implemented a useEffect hook to keep track of props.hidden and adjust actualHidden accordingly (with introduced delays to not cut off an animation short).

The code works well when hiding the modal, but for some reason does not animate the appearing of the modal (instead just makes the modal appear instantaneously).

Why is this happening and how can I fix it?

Modal.js

export default function Modal(props) {
    const [actualHidden, setActualHidden] = useState(true)
    useEffect(() => {
        if(props.hidden === false) {
            setActualHidden(false)
        }
        else {
            setTimeout(() => setActualHidden(true), 300)
        }
    }, [props.hidden])

    return (
    <>
        <div
        className={`fixed ${props.hidden ? "opacity-0" : "opacity-100"} ${actualHidden ? "hidden" : ""} inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full transition-all duration-300`}
        onClick={() => props.setHidden(true)}
        >
            additional modal html...
        </div>
    </>
    );
}
1 Answers

I'm guessing it's because you set the opacity to 1 before the component has mounted. So the css property isnt going from 0 to 1, it just starts at 1 and so the transition doesnt fire. Try setting both properties at the same time in the same fn without relying on the useEffect. Eg having setHidden(false) and setActualHidden("") in the same fn.

Alternatively use a fade in keyframes css animation on the modal so it fades in whenever it mounts.

EDIT: Here is an example of what I mean. You have to handle fading out the div manually, but you don't need to fade the component in as the animation css property will handle that whenever the div mounts. Example: (Note the css is in index.html): https://stackblitz.com/edit/react-r5bqg7?file=src%2FApp.js App.js

import React from 'react';
import './style.css';

export default function App() {
  const [display, setDisplay] = React.useState('none');
  const [opacity, setOpacity] = React.useState(1);

  const toggleVisibility = () => {
    if (display === 'none') {
      setDisplay('');
      setOpacity(1)
    } else {
      setOpacity(0);
      setTimeout(() => setDisplay('none'), 3000);
    }
  };

  return (
    <>
      <div className="box" style={{ display, opacity }}>
        Hello
      </div>
      <button onClick={toggleVisibility}>Toggle Visibility</button>
    </>
  );
}

index.html

<style>
  @keyframes fadeIn {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }

  .box {
    width: 100px;
    height: 100px;
    background-color: green;
    transition: opacity 3s;
    animation: fadeIn 3s;
  }
</style>
Related