Framer Motion animate parent FORM element if form button is clicked

Viewed 184

I recently started working with Framer Motion. I really like it, and it makes it a lot easier to animate divs and add page transitions. I am having a problem where I am checking if my form is toggled (open/closed) to animate that parent <form> tag using Framer Motion. <motion.form> However, I am already inside the check if the Toggle is active and this way it's firing and checking for two things the animation and toggle state.

How can I simply animate the form if toggleForm is active?

Parent Article.tsx:

import React, { useState, useCallback } from "react";

import { NextPage } from "next";
import Head from "next/head";
import Link from "next/link";

import VideoModule from "@modules/VideoModule";
import HeroModule from "@modules/HeroModule";
import SliderModule from "@modules/SliderModule";
import ImageModule from "@modules/ImageModule";
import QuoteModule from "@modules/QuoteModule";
import PreFooterModule from "@modules/PreFooterModule";

import Footer from "@components/Footer";
import CommentForm from "@components/CommentForm";

const Article: NextPage = () => {
  const closeFormText = "Ik reageer later";
  const respondFormText = "Ik wil reageren";

  const [buttonText, setButtonText] = useState(respondFormText);
  const [toggleForm, setToggleForm] = useState(false);

  const onToggleForm = useCallback(() => {
    setToggleForm(!toggleForm);

    !toggleForm ? setButtonText(closeFormText) : setButtonText(respondFormText);
  }, [toggleForm, setToggleForm]);

  return (
    <>
      <Head>
        <title>Artikel Detail</title>
        <meta name="author" content="" />
        <meta name="description" content="Developed by Friends For Brands" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <HeroModule
        title="De headline van deze tekstuele content"
        text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
      />

      <div className="max-w-screen-xl px-6 pt-16 pb-12 mx-auto border-navyBlue border-b-1 lg:px-8 text-navyBlue ">
        <div className="grid w-full grid-cols-12 gap-6">
          <aside className=" lg:col-span-3 col-span-full">
            <div className="grid grid-cols-12 lg:sticky lg:top-5 lg:block">
              <div className="col-span-2 mb-4 avatar">
                <div className="w-16 h-16 sm:h-20 sm:w-20 md:w-24 md:h-24 rounded-full ring ring-[#65c3c8] ring-offset-base-100 ring-offset-2">
                  <img src="https://i.pravatar.cc/300" alt="" />
                </div>
              </div>

              <div className="col-span-8 leading-2">
                <p className="font-bold">John Doe</p>
                <p>Marketing Manager</p>

                <p className="mt-2 md:mt-6">00/00/0000</p>
                <p>Leestijd 10 minuten</p>

                <div className="mt-6 card-actions">
                  <Link href="/tag/fashion" passHref>
                    <span className="text-[11px] font-semibold uppercase cursor-pointer badge badge-outline hover:bg-navyBlue hover:text-white">
                      Fashion
                    </span>
                  </Link>
                  <Link href="/tag/products" passHref>
                    <span className="text-[11px] font-semibold uppercase cursor-pointer badge badge-outline hover:bg-navyBlue hover:text-white">
                      Products
                    </span>
                  </Link>
                </div>
              </div>
            </div>
          </aside>
          <main className="leading-relaxed col-span-full lg:col-span-9 md:text-normal">
            <p>
              Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
              enim ad minim veniam, quis nostrud exercitation ullamco laboris
              nisi ut aliquip ex ea commodo consequat.
            </p>

            <h2 className="mt-3 text-3xl md:text-4xl">Koptekst H2</h2>
            <h3 className="mt-3 text-2xl md:text-3xl">Koptekst H3</h3>
            <h4 className="mt-3 text-xl md:text-2xl">Koptekst H4</h4>

            <ul className="pl-6 my-4 list-disc">
              <li>Lorem ipsum dolor sit amet</li>
              <li>
                Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
                labore
              </li>
              <li>Et dolore magna aliqua. Ut enim ad minim veniam</li>
              <li>
                Quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
                commodo consequat.
              </li>
            </ul>
            <h2 className="mt-6 text-3xl md:text-4xl">Tussentitel</h2>
            <ul className="pl-6 my-4 list-decimal">
              <li>Lorem ipsum dolor sit amet</li>
              <li>
                Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
                labore
              </li>
              <li>Et dolore magna aliqua. Ut enim ad minim veniam</li>
              <li>
                Quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
                commodo consequat.
              </li>
            </ul>
            <h5 className="font-bold">Tussentitel paragraaf</h5>
            <p>
              Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
              enim ad minim veniam, quis nostrud exercitation ullamco laboris
              nisi ut aliquip ex ea commodo consequat.
            </p>
            <h3 className="mt-6 text-2xl md:text-3xl">Video Module</h3>
            <React.StrictMode>
              <VideoModule id="mkggXE5e2yk" platform="youtube" />
            </React.StrictMode>
            <p>
              Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
              enim ad minim veniam, quis nostrud exercitation ullamco laboris
              nisi ut aliquip ex ea commodo consequat.
            </p>
            <h3 className="mt-6 text-2xl md:text-3xl">Image Module</h3>
            <ImageModule
              url="https://images.pexels.com/photos/1193743/pexels-photo-1193743.jpeg"
              caption="Photo of multicolored abstract painting"
              alt="A Pexels image"
            />
            <p>
              Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
              enim ad minim veniam, quis nostrud exercitation ullamco laboris
              nisi ut aliquip ex ea commodo consequat.
            </p>
            <h3 className="mt-6 text-2xl md:text-3xl">Slider Module</h3>
            <SliderModule />
            <p>
              Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
              eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
              enim ad minim veniam, quis nostrud exercitation ullamco laboris
              nisi ut aliquip ex ea commodo consequat.
            </p>
            <h3 className="mt-6 text-2xl md:h2text-3xl">Quote Module</h3>
            <QuoteModule />
          </main>
        </div>
      </div>

      <CommentForm
        buttonText={buttonText}
        toggleForm={toggleForm}
        clickHandle={onToggleForm}
      />

      <PreFooterModule />
      <Footer />
    </>
  );
};

export default Article;

Child component CommentForm.tsx:

import { motion } from "framer-motion";

const variants = {
   open: { opacity: 1, x: 0 },
   closed: { opacity: 0, x: "-100%" },
}

interface CommentFormProps {
  buttonText: string;
  toggleForm: boolean;
  clickHandle: any;
}

const CommentForm = ({
  buttonText,
  toggleForm,
  clickHandle,
}: CommentFormProps) => (
  <div className="w-full max-w-4xl px-4 py-20 mx-auto">
    <div className="flex gap-4">
      <h2 className="text-2xl md:text-4xl">Reacties</h2>
      <button
        onClick={clickHandle}
        className={`inline-flex px-4 py-2 place-items-center text-xs font-medium uppercase transition duration-150 ease-in-out border rounded-full cursor-pointer border-navyBlue hover:bg-navyBlue hover:text-white md:mt-1`}
      >
        {buttonText}
      </button>
    </div>

    {toggleForm && (
      <motion.form className="pt-8" autoComplete="off" animate {toggleForm ? "open" : "closed"}
  variants={variants}>
        <div className="grid xl:grid-cols-2 xl:gap-6">
          <p className="mb-5 font-bold col-span-full md:m-0">Jouw gegevens</p>

          <div className="relative z-0 w-full mb-6">
            <input
              type="text"
              name="first_and_lastname"
              id="first_and_lastname"
              className="block w-full py-3 text-sm bg-transparent border-0 appearance-none text-navyBlue border-b-1 border-navyBlue focus:outline-none focus:ring-0 focus:border-bubblegum peer"
              placeholder=" "
              required
            />
            <label
              htmlFor="first_and_lastname"
              className="absolute text-md text-navyBlue duration-200 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:left-0 peer-focus:text-navyBlue peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:uppercase peer-focus:scale-75 peer-focus:-translate-y-6"
            >
              Voor- en Acthernaam
            </label>
          </div>

          <div className="relative z-0 w-full mb-6">
            <input
              type="email"
              name="email"
              className="block w-full py-3 text-sm bg-transparent border-0 appearance-none text-navyBlue border-b-1 border-navyBlue focus:outline-none focus:ring-0 focus:border-bubblegum peer"
              placeholder=" "
              required
            />
            <label
              htmlFor="email"
              className="absolute text-md text-navyBlue duration-200 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:left-0 peer-focus:text-navyBlue peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:uppercase peer-focus:scale-75 peer-focus:-translate-y-6"
            >
              E-mailadres
            </label>
          </div>
        </div>

        <div className="relative z-0 w-full mt-2 mb-6">
          <textarea
            id="comment"
            name="comment"
            rows={5}
            className="block w-full py-5 text-3xl bg-transparent border-0 appearance-none resize-none text-navyBlue border-b-1 border-navyBlue focus:outline-none focus:ring-0 focus:border-bubblegum peer"
            placeholder=" "
            required
          />

          <label
            htmlFor="comment"
            className="absolute text-md text-navyBlue duration-200 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:left-0 peer-focus:text-navyBlue peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:uppercase peer-focus:scale-75 peer-focus:-translate-y-6"
          >
            Reactie
          </label>
        </div>

        <button
          type="submit"
          className="items-center justify-center px-6 py-1.5 text-sm font-medium uppercase transition duration-150 ease-in-out bg-transparent border rounded-full shadow-sm md:text-lg text-navyBlue border-navyBlue hover:bg-navyBlue hover:text-white"
        >
          Reageer op artikel
        </button>
      </motion.form>
    )}
  </div>
);

export default CommentForm;
1 Answers

You're using the toggleForm state to change the animate property on the form, but you're also using it to conditionally render the form. So as soon as that state switches to false, the form is removed from the DOM, without having a chance to animate to the "closed" variant.

{toggleForm && (
      <motion.form className="pt-8" autoComplete="off" animate {toggleForm ? "open" : "closed"}
  variants={variants}>
 //...etc
)}

The easiest fix would be remove the conditional rendering, and just use the state to change the animation variant on the motion.form element ("open" or "closed").

If you need to actually remove the form from the DOM like that, you can use AnimatePresence with an exit animation (don't forget to give the form a unique key prop!) to have the the element perform an animation before getting removed from the DOM.

Related