'When new items are added, the first existing items are not animated using framer motion

When there are only two items initially, clicking Add will add new item (3rd item). The 2nd item will go below and show a heading. Going below is not animated.

In the same way, when there are 3 items and clicked Delete, the last movement is not animated.

Demo with full source can be found here: https://codesandbox.io/s/practical-dream-229d2y?file=/src/App.tsx

This is the source code for archive purposes:

import { useState } from "react";
import { AnimatePresence, AnimateSharedLayout, motion } from "framer-motion";

function SomeItem() {
  return (
    <div
      style={{
        border: "1px solid blue",
        padding: "12px",
        height: 60,
        width: "100%",
        margin: "12px"
      }}
    >
      {new Date().toISOString()}
    </div>
  );
}

export default function App() {
  const [items, setItems] = useState<typeof SomeItem[]>([SomeItem, SomeItem]);

  return (
    <div className="App">
      <button
        onClick={() =>
          setItems((prev) => prev.filter((_p, i) => i !== prev.length - 1))
        }
      >
        Delete
      </button>
      <button onClick={() => setItems((prev) => [...prev, SomeItem])}>
        Add
      </button>

      <section>
        <h3>What&apos;s not working?</h3>
        <p>
          When there are only two items initially, clicking Add will add new
          item (3rd item). The 2nd item will go below and show a heading. Going
          below is not animated.
        </p>
        <p>
          In the same way, when there are 3 items and clicked Delete, the last
          movement is not animated.
        </p>
      </section>

      <AnimateSharedLayout>
        <ul style={{ listStyle: "none", margin: 0, padding: 0 }}>
          <AnimatePresence initial>
            {items.map((Item, i) => (
              <motion.li
                key={i}
                layout
                animate={{ transition: { damping: 1000 }, y: 0 }}
                exit={{ opacity: 0, transition: { damping: 1000 }, y: "-110%" }}
                initial={{ y: "-110%" }}
              >
                {i !== 0 && items.length > 2 && (
                  <motion.h3
                    animate={{ transition: { damping: 0 }, x: 0 }}
                    exit={{ x: "-100%" }}
                    initial={{ x: "-100%" }}
                  >
                    Item {i}
                  </motion.h3>
                )}
                <Item />
              </motion.li>
            ))}
          </AnimatePresence>
        </ul>
      </AnimateSharedLayout>
    </div>
  );
}

Demo of the not working animation



Solution 1:[1]

The problem seems to be at this conditional i !== 0 && items.length > 2, when the items.length is greater than two it's rendering two headers at same time because you specified to only render the header after the item of index 0, thus when you delete the item it's also deleting the two headers at same time and just animating the last one. To fix it you can change the conditional to i !== 0 && items.length > 1, this way it will render item 1, item 2 and so on. see the working example

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Yago Biermann