motion icon indicating copy to clipboard operation
motion copied to clipboard

[FEATURE] Apply `staggerChildren` to components entering into an active variant

Open damianszocik opened this issue 5 years ago • 8 comments

1. Describe the bug

Children components wrapped in AnimatePresence don't stagger as they should if they are not rendered initially. In provided example, you can see a comparison between child components mapped initially from static data array (works fine) and rendered conditionally, e.g. after some remote data is fetched, which is a common scenario.

2. CodeSandbox reproduction of the bug https://codesandbox.io/s/staggerchildrenissue-tkkie?file=/src/App.js

3. Steps to reproduce Refresh provided codesandbox example.

4. Expected behavior Children components rendered conditionally should be staggered the same as the ones rendering initially.

5. Environment details KDE neon 5.19, Chrome 85.0.4183.102 (Official Build) (64-bit)

damianszocik avatar Sep 23 '20 20:09 damianszocik

staggerChildren currently only works when the parent variant actually changes, though I can see how this would be useful.

mattgperry avatar Nov 02 '21 14:11 mattgperry

Is there a workaround for this use case? I think this is a very common case.

focux avatar Jul 13 '22 16:07 focux

Any resolution on this ?

snehasis8 avatar Aug 26 '22 06:08 snehasis8

It makes no sense if it's not able to perform on the dynamic data.

snehasis8 avatar Aug 26 '22 06:08 snehasis8

Facing the same issue as well – for some reason dynamically fetched data (from localStorage, with useSWR) don't stagger, even when I set a unique key for each of the list items 😕

Edit: Inspired by @mattgperry's comment above, I figured out the following workaround – by setting a key on the parent element to the length of the dynamic data, something like this:

<motion.ul
   key={data.length}
          initial="hidden"
          animate="show"
          variants={{
            hidden: {},
            show: {
              transition: {
                staggerChildren: 0.1,
              },
            },
          }}
>
  {data.map((d) => (
      <motion.li
      variants={variants}
     >
         {d.name}
     </motion.li>
  )}
</motion.ul>

steven-tey avatar Sep 04 '22 01:09 steven-tey

Edit: Inspired by @mattgperry's comment above, I figured out the following workaround – by setting a key on the parent element to the length of the dynamic data, something like this:

Yes but that only helps if the parent did not have their own transitions. A key change would reset this transition.

The use of this feature is limited if it did not work if children get added after the parent transition start.

I have made a small Sandbox for demonstration for my ticket: Example Toggle Demo1: Expected behavior Toggle Demo2: Example with children added after timeout

TimoP avatar Sep 20 '22 14:09 TimoP

Same issue here! I need to load a list of items dynamically with a "load more" button... This is my workaround:

const itemVariants = (index) => ({
  initial: {
    opacity: 0,
    y: -50 + index * 5,
  },
  animate: {
    opacity: 1,
    y: 0,
    transition: {
      delay: 0.025 * index,
    },
  },
});

<motion.div initial="initial" animate="animate" exit="initial">
  {items.map((item, index) => (
    <motion.span
      key={item.id}
      variants={itemVariants(index)}
    >
      Item #{index}
    </motion.span>
  ))}
</motion.div>

DaviidMM avatar Aug 20 '24 19:08 DaviidMM

Facing the same issue as well – for some reason dynamically fetched data (from localStorage, with useSWR) don't stagger, even when I set a unique key for each of the list items 😕

Edit: Inspired by @mattgperry's comment above, I figured out the following workaround – by setting a key on the parent element to the length of the dynamic data, something like this:

<motion.ul
   key={data.length}
          initial="hidden"
          animate="show"
          variants={{
            hidden: {},
            show: {
              transition: {
                staggerChildren: 0.1,
              },
            },
          }}
>
  {data.map((d) => (
      <motion.li
      variants={variants}
     >
         {d.name}
     </motion.li>
  )}
</motion.ul>

Thank for this workaround 🥇. I almost uninstall framer-motion 😆

Jervx avatar Mar 04 '25 10:03 Jervx

Hello, I try for my project the @steven-tey solution works fine. But if u want to dynamically delete some elements, it gonna re-render the container and the animations. So this solutions isn't good at 100%. As you can see on my short video : https://github.com/user-attachments/assets/f80e1ee5-2452-4bcd-85c5-bcc593b582c1 (sorry for the bad quality)

Regard, Axel

AxelFertinel avatar Jul 03 '25 13:07 AxelFertinel