Animations in Framer Motion

I was trying to implement Framer Motion, to understand how it works, in a personal project of mine.

For now, I wrote the following code (100% working), in these two components:

  • Modal.jsx

    import { createPortal } from "react-dom";
    import { motion } from "framer-motion";
    
    export default function Modal({ title, children, onClose }) {
      return createPortal(
        <>
          <div className="backdrop" onClick={onClose} />
          <motion.dialog
            variants={{
              open: { opacity: 1, translateY: 0 },
              closed: { opacity: 0, translateY: 30 },
            }}
            initial="closed"
            animate="open"
            exit="closed"
            open
            className="modal"
          >
            <h2>{title}</h2>
            {children}
          </motion.dialog>
        </>,
        document.getElementById("modal")
      );
    }
    
  • NewChallenge.jsx

    import { useContext, useRef, useState } from "react";
    import { motion } from "framer-motion";
    
    import { ChallengesContext } from "../store/challenges-context.jsx";
    import Modal from "./Modal.jsx";
    import images from "../assets/images.js";
    
    export default function NewChallenge({ onDone }) {
      const title = useRef();
      const description = useRef();
      const deadline = useRef();
    
      const [selectedImage, setSelectedImage] = useState(null);
      const { addChallenge } = useContext(ChallengesContext);
    
      function handleSelectImage(image) {
        setSelectedImage(image);
      }
    
      function handleSubmit(event) {
        event.preventDefault();
        const challenge = {
          title: title.current.value,
          description: description.current.value,
          deadline: deadline.current.value,
          image: selectedImage,
        };
    
        if (
          !challenge.title.trim() ||
          !challenge.description.trim() ||
          !challenge.deadline.trim() ||
          !challenge.image
        ) {
          return;
        }
    
        onDone();
        addChallenge(challenge);
      }
    
      return (
        <Modal title="New Challenge" onClose={onDone}>
          <form id="new-challenge" onSubmit={handleSubmit}>
            <p>
              <label htmlFor="title">Title</label>
              <input ref={title} type="text" name="title" id="title" />
            </p>
    
            <p>
              <label htmlFor="description">Description</label>
              <textarea ref={description} name="description" id="description" />
            </p>
    
            <p>
              <label htmlFor="deadline">Deadline</label>
              <input ref={deadline} type="date" name="deadline" id="deadline" />
            </p>
    
            <motion.ul
              id="new-challenge-images"
              variants={{
                open: { transition: { staggerChildren: 0.05 } },
              }}
              initial="closed"
              animate="open"
            >
              {images.map((image) => (
                <motion.li
                  key={image.alt}
                  onClick={() => handleSelectImage(image)}
                  className={selectedImage === image ? "selected" : undefined}
                  variants={{
                    closed: { opacity: 0, scale: 0 },
                    open: { opacity: 1, scale: 1 },
                  }}
                >
                  <img {...image} />
                </motion.li>
              ))}
            </motion.ul>
    
            <p className="new-challenge-actions">
              <button type="button" onClick={onDone}>
                Cancel
              </button>
              <button>Add Challenge</button>
            </p>
          </form>
        </Modal>
      );
    }
    

All animations, after various tests, work correctly, in particular:

  • The animation in Modal.jsx, animates the <dialog> with a fade-in-from-bottom style animation

  • The animations in New Challenge.jsx animates the list <ul> by setting an animation delay on the elements of the list with staggerChildren and the list items <li> with a bounce style animation.

As mentioned above, everything works and the animations are triggered correctly.

What I wanted to understand is, where does the list <ul> get the initial state “closed” from? From the Modal?

Is there a better way to do what I’m trying to do?

I am not an expert in using this library, but I find it very useful and would like to learn how to use it in the best possible way.

Could anyone help me understand?