Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] AnimatePresence doesn't update with the latest state if the state changes fast. #2023

Closed
kyung-lee-official opened this issue Mar 16, 2023 · 67 comments · Fixed by #2257
Labels
bug Something isn't working

Comments

@kyung-lee-official
Copy link

kyung-lee-official commented Mar 16, 2023

Describe the bug

AnimatePresence doesn't update with the latest state if the state changes fast.

Provide a CodeSandbox reproduction of the bug

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

const ComponentWrapper = (props: any) => {
	const { status } = props;
	return (
		<motion.div
			initial={{ opacity: 1 }}
			animate={{ opacity: 1 }}
			exit={{ opacity: 0 }}
		>
			<h1>{status}</h1>
		</motion.div>
	);
};

function ApBug() {
	const [status, setStatus] = useState<number>(1);
	return (
		<div>
			<AnimatePresence mode="wait">
				<ComponentWrapper key={status} status={status} />
			</AnimatePresence>
			<h1>status: {status}</h1>
			<button
				onClick={() => {
					setStatus(status + 1);
				}}
			>
				+1
			</button>
		</div>
	);
}

export default ApBug;

CodeSandbox

Steps to reproduce

If the +1 button was clicked slowly, the animation will update with the state. While if the button was clicked swiftly, the animation stops updating with the state.

Expected behavior

The animation should update with the state.

Video or screenshots

If applicable, add a video or screenshots to help explain the bug.

2023-03-16.23-03-50.mp4

Environment details

OS:
Edition Windows 11 Pro
Version 22H2
Installed on ‎11/‎10/‎2022
OS build 22621.1413
Experience Windows Feature Experience Pack 1000.22639.1000.0

Browser:
Chrome
Version 111.0.5563.65 (Official Build) (64-bit)

npm package versions:

{
  "name": "animation-sandbox",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "framer-motion": "^10.4.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.9.0",
    "styled-components": "^5.3.9"
  },
  "devDependencies": {
    "@types/react": "^18.0.28",
    "@types/react-dom": "^18.0.11",
    "@types/styled-components": "^5.1.26",
    "@vitejs/plugin-react": "^3.1.0",
    "typescript": "^4.9.5",
    "vite": "^4.2.0"
  }
}
@kyung-lee-official kyung-lee-official added the bug Something isn't working label Mar 16, 2023
@kyung-lee-official kyung-lee-official changed the title [BUG] [BUG] AnimatePresence doesn't update with the latest state if the state changes fast. Mar 16, 2023
@ndimatteo
Copy link

ndimatteo commented Mar 20, 2023

I'm getting the same issue on the latest version of framer motion 10.6.1

Screen.Recording.2023-03-20.at.9.27.15.AM.mov

You can see the _actual_ current index and caption below the framer animation. The moment I click faster than the animation takes, framer gets out of sync, despite a unique key being applied per the docs.

@EvHaus
Copy link

EvHaus commented Mar 20, 2023

This is also happening for me as well after upgrading from 10.3.4 to 10.4.0. Looks like it was introduced as a regression in 10.4.0. It's especially easy to reproduce in unit tests where click events happen very quickly.

@ndimatteo
Copy link

I'm actually seeing this in a site running on 9.1.7 as well, so I'm not sure if this is a regression in v10 or just a long-standing bug?

There's no reason that if the key is unique that nodes shouldn't be getting removed/added properly, regardless of how fast changes are happening. If that's not the case, that should be made clear in the docs 😬

@albinekman91
Copy link

albinekman91 commented Apr 6, 2023

I'm having the same issue switching between two components based on a state update: reproduction

Works fine on ^9.0

@benjaminwaterlot
Copy link

I've got the exact same issue and found the regression version:

→ Works fine on 9.0.2
→ Breaks on 9.0.3

@michaeljblum
Copy link

I'm experiencing the same issue on ^10.9.1.

Would rather not roll back to v9 because clipPath anims only work in more recent versions for me.

Anyone got a clue what broke? Like others said above it doesn't make much sense if key is reliably changing.

Anyway thanks for the sanity, confirmation. Not having any AnimatePresence problems on 9.0.2.

@varbrad
Copy link

varbrad commented Apr 21, 2023

Also experiencing the same issue on one of our projects where a modal stack state occasionally changes quite frequently and causes AnimatePresence to get out-of-sync and make our UI get all glitchy/frozen.

We're actually on version 6.5.1 (Stuck on React 17 😬) but I did a force-update to the latest version (10.12.4 at the time of writing) and the behaviour was the same.

@ndimatteo
Copy link

@mattgperry I think this is related to some tests in a PR you recently merged (#2119)...

I'm still seeing this issue persist even on the latest release (10.12.11).

Rapid changes cause items to not enter/exit properly on subsequent changes.

This is causing a real fuss in production, especially when you start doing scroll-related animations:

  1. Previously removed motion components are not returning after rapid exits from AnimatePresence
  2. Rapid key changes cause the current motion component to "stick" for subsequent changes, and then disappear for others

It's difficult to pinpoint what framer-motion is doing behind the scenes, but I can 100% confirm that the key is unique and properly updated regardless of how fast the change happens, but it seems that framer motion can't keep up or resync itself once the dust has settled.

@ndimatteo
Copy link

It does seem to be exclusively a problem with mode="wait" if that helps! 🤘

@ndimatteo
Copy link

Also for those still experiencing this, here's a quick workaround that seems to work well:

<AnimatePresence mode="popLayout" initial={false}>
  // your unique motion component
</AnimatePresence>

And then add a delay to your animate transition that is >= to your exit transition.

Rapid changes do not experience the same degradation described in this thread 😎

@pofixifop
Copy link

I also have this maddening problem! popLayout is not a viable solution as it seems to actually pop the component outside of its normal layout, thereby not obeying a parent's overflow:hidden setting

@dpacmittal
Copy link

dpacmittal commented Jun 1, 2023

Downgrading to 9.0.2 fixed the issue for me. Thanks @benjaminwaterlot!

@paavohuhtala
Copy link

paavohuhtala commented Jun 12, 2023

I can confirm this still happens in the latest version. We have an animated modal dialog based on <AnimatePresence> which gets permanently stuck if the user closes it too soon after opening. Rolling back to 9.0.2 fixes the issue.

@randex
Copy link

randex commented Jun 15, 2023

Same issue on the latest version (10.12.16).

<AnimatePresence initial={false}>
    <div key={uuid}>
        ...
    </div>
<AnimatePresence>

Switching the uuid triggers the animation of that div, and if switching it fast enough, it breaks and doesn't get removed, so I see 2 divs stacked on each other: the current one and its previous state that bugged out. Rolling back to 9.0.2 fixes the issue for me too.

@squidfunk
Copy link

Same problem here. Last version working for me is 10.5.0. 10.6.0 breaks.

@karelhojda
Copy link

karelhojda commented Jun 28, 2023

10.12.17 still getting stucked

@owjs3901
Copy link

10.12.22 still have bug.

@pofixifop
Copy link

10.13.0 still has the issue

@owjs3901
Copy link

This is a very important issue.

However, I have no idea what efforts Framer is making to fix this bug.
I am not even sure if they are aware of this bug.

I would like to know the roadmap or plan to fix this bug.

@pofixifop
Copy link

this bug is driving me crazy and comes up randomly in a not-always-reproducible fashion in production code where timing is a factor of client browser, backend job duration, react component states updating...

My current solution is to just comment out all exit props from variants and motion elements, which is obviously not ideal and ruins 50% of the reason we use framer-motion in the first place. It's making me seriously consider migrating away from this library completely, as animating a component while exiting and letting an entering component (often times the final value of some task that you want to display to the user) take its place is a huge deal, and can not break in a random fashion in production code! :(

@owjs3901
Copy link

@pofixifop If the migration target is decided, please share it.

We would also like to consider our organization.

@GianlucaGuarini
Copy link
Contributor

@pofixifop @owjs3901 probably you can try to fix it with a PR instead of migrating your whole project to another library.

Framer motion is an open source project and the maintainer relies on the help of the community to fix such problems. Please consider contributing to the project ✌️

@hatsumatsu
Copy link

Okay, let's try help fix this.

Here is a minimal reproduction showing the issue:
https://codesandbox.io/s/framer-motion-animatepresence-bug-w4ycgz?file=/src/App.js

Trying out different version of framer-motion , I can not confirm the 10.5.0 vs 10.6.0 theory.
However @benjaminwaterlot 's 9.0.2 vs 9.0.3-alpha.0 theory seems to be correct.

There seems to be a refactor of the ExitAnimation component between these versions:
v9.0.2...v9.0.3-alpha.0

Anyone able to dig deep into this? 😇

@GianlucaGuarini
Copy link
Contributor

GianlucaGuarini commented Jul 26, 2023

The problem might be in this method.

Probably one of these early returns should be patched. I am at moment not able to work on it but hope that it might help anyone else debugging the issue.

@pofixifop
Copy link

thanks for the attention guys 🥰

@hatsumatsu the minimal repro sandbox is great - very concise and clearly showcases the issue

@mattgperry let's do this!! 💪

@owjs3901
Copy link

I was ready to workspace to fix issue.

The childrenToRender in AnimatePresence.tsx/index.tsx is an array, that contains the rendered elements.
Generally length of variable maintain 1, And In transition It will be 2.
But if the motion is done too fast, length will be over 1 at the moment of stopping.

I will Investigate why don't reduce this length.

If there are any additional achievements, I will comment.

image

GianlucaGuarini added a commit to GianlucaGuarini/motion that referenced this issue Jul 27, 2023
@perrmadiafrrian
Copy link

Any update on this?

@federicocappellotto97
Copy link

Any update on this?

+1

@hatsumatsu
Copy link

Curious if this doesn't affect paying framer users as well. Seems to affect such an important core functionality.

@jason-dark
Copy link

jason-dark commented Oct 2, 2023

Also noticed this bug. Using framer 10.16.4. Downgraded to 10.5.0 which partially solves the problem for me.

I have a Nextjs project that has a paginated list on a page. When fetching data for the next page I display a loading state. When the data is returned, I hide the loading state and show the data in a list.

I am using AnimatePresence with mode='wait' to nicely transition between the loading state and the list.

However, due to Nextjs caching, if I make a request that has already been made (e.g. went from page 1 to page 2, then back to page 1), the response is nearly immediate. Because this is so fast Framer doesn't seem to pick up the state change and the loading state hangs indefinitely.

Downgrading to 10.5.0 at least eliminates the indefinite loading state issue, but there is still no animation when the response is too fast, resulting in a state change that is too fast.

@michaeljblum
Copy link

Is there a concrete reason this issue has gone unresolved for so long? Phrased differently, is there a compelling reason not to expect a fix for this issue? (Intended behavior, unintended but necessary outcome of more important logic that cannot be changed without causing more global issues, phasing out of FM maintenance to work on paid Framer products, etc...)

FM hits the absolute sweet spot of intuitive API and powerful/performant anims. But this is increasingly feeling like a deal breaker - if same-page enter/exit transitions are employed with the 'wait' mode applied anyway. I understand this is open source and with this comes no expectations of fixes, speedy or otherwise. Writing mainly because I'd just really prefer sticking with FM as my main React anim library.

@gerardmarquinarubio
Copy link

Still having this issue on 10.16.4. Any workarounds?

@devweissmikhail
Copy link

devweissmikhail commented Oct 8, 2023

Still having this issue on 10.16.4. Any workarounds?

I tried version 10.5.0 but noticed a similar issue.
However, upgrading to version 9.0.2 solves this problem
(I use mode="wait")

@AtoLrn
Copy link

AtoLrn commented Oct 13, 2023

I am having the same behavior with Remix and the Outlet component, when I stay on the same page but changes the key everythings works fine. But if I change a page (implies the Outlet component to render a new child) the exit animations will skip. I noticed that on version 10.16.4 something blocks the re-render and let the key unchanged (works fine in 9.0.2 but with the exit skip).

@codingwithdidem
Copy link

10.12.22 have this problem too

@sikandarchishty
Copy link

I had the same issue with mode="wait", where I was changing images in a slider where I was quickly changing images with button click.

Use mode="popLayout" on AnimatePresence, works for me.

@drscottlobo
Copy link

I'm having the same issue 10.16.4

@AndonMitev
Copy link

I can spot some issue as well with mode="popLayout"

Having this example:

const contentVariants = {
  hidden: { opacity: 0 },
  visible: { opacity: 1 },
};


  <AnimatePresence mode="popLayout">
          {tab === TABS.SEND ? (
            <motion.div
              className="w-full"
              key="send"
              initial="hidden"
              animate="visible"
              exit="hidden"
              variants={contentVariants}
              transition={{ duration: 0.5 }}
            >
              <Send />
            </motion.div>
          ) : (
            <motion.div
              className="w-full"
              key="receive"
              initial="hidden"
              animate="visible"
              exit="hidden"
              variants={contentVariants}
              transition={{ duration: 0.5 }}
            >
              <Receive />
            </motion.div>
          )}
        </AnimatePresence>

when switching between tabs kinda fast, content might get mixed and lets say i'm on Send tab, i can see Send and Receive content under the same tab any ideas

mattgperry pushed a commit to GianlucaGuarini/motion that referenced this issue Nov 13, 2023
mattgperry pushed a commit to GianlucaGuarini/motion that referenced this issue Nov 13, 2023
mattgperry pushed a commit that referenced this issue Nov 14, 2023
@hatsumatsu
Copy link

@mattgperry Thanks so much for merging the PR! In 10.16.5 the issue is solved in my test case:

https://codesandbox.io/s/framer-motion-animatepresence-bug-w4ycgz

@didemkkaslan
Copy link

thanks @mattgperry 10.16.5 works for me too 🎉

@gorchov
Copy link

gorchov commented Nov 15, 2023

10.16.5 fixed it.

@rjpaul2
Copy link

rjpaul2 commented Nov 16, 2023

10.16.5 fixed for me too. Thanks @mattgperry

@Pdhenrique
Copy link

Pdhenrique commented Nov 17, 2023

I'm having this problem using mode="wait". In my case it is used in an Accordion component. I tested some past cases previously but none with success, and removing the mode="wait" from the accordions when clicked quickly stops them from updating correctly and no longer closes.

The error is not visible on the screen, but it appears when I'm building the project.

Framer version used: 10.16.5

 const variants = {
  open: { height: 'auto' },
  closed: { height: 0 }
}

export const Accordion: FC<AccordionProps> = memo(
  ({
    'data-testid': datatestId = 'accordion',
    align,
    children,
    disabled,
    divider,
    expanded = false,
    header,
    onExpand,
    id,
    ...props
  }) => {
    const accordionContentRef = useRef<HTMLDivElement>(null)
    const MotionBox = motion(Box)
    const headerElement = () => {
      if (typeof header === 'string') {
        return (
          <AccordionHeader
            align={align}
            data-testid={`${datatestId}-header`}
            disabled={disabled}
            divider={divider}
            expanded={expanded}
            onExpand={onExpand}
            id={id}
          >
            <Text
              color={disabled ? 'neutralMedium60' : 'neutralDark80'}
              data-testid={`${datatestId}-header--content-children-text`}
              fontSize="xs"
              fontWeight="semibold"
              letterSpacing="0.1px"
              lineHeight="24px"
              overflow="hidden"
              textOverflow="ellipsis"
              whiteSpace="nowrap"
            >
              {header}
            </Text>
          </AccordionHeader>
        )
      }
      return header
    }

    return (
      <StyledAccordion
        data-testid={datatestId}
        display="flex"
        flexDirection="column"
        {...props}
      >
        {headerElement()}
        <AnimatePresence>
          {expanded && (
            <MotionBox
              animate={expanded ? 'open' : 'closed'}
              exit="closed"
              initial="closed"
              style={{ overflow: 'hidden' }}
              variants={variants}
              transition={{
                when: 'beforeChildren',
                height: {
                  type: 'tween',
                  duration: 0.3,
                  ease: 'easeOut'
                }
              }}
            >
              <Box
                data-testid={`${datatestId}--content`}
                ref={accordionContentRef}
              >
                {children}
              </Box>
            </MotionBox>
          )}
        </AnimatePresence>
        {divider && (
          <Divider
            borderColor="neutralMedium40"
            data-testid={`${datatestId}--divider`}
            mt={expanded ? 'quark' : 0}
            opacity={expanded ? 1 : 0}
          />
        )}
      </StyledAccordion>
    )
  }
)

@Ou7law007
Copy link

I don't think this is fixed on 10.16.5.

If a page change with router is involved, and the page is changed very quickly back and forth, the page and url desync which causes a very weird bug. The entire page bugs out making it render the wrong component router element for a url route.

I don't know if this is a different issue though. I've had it for a while and 10.16.5 did not fix it.

I'm unable to make a sandbox example at this moment, maybe someone else who's having this issue could confirm.

@liho00
Copy link

liho00 commented Nov 21, 2023

Same problem here. Last version working for me is 10.5.0. 10.6.0 breaks.

10.16.5 still not working, 10.12.2 is working

@michaeljblum
Copy link

Fixed for me!

Literally just ran into this issue for like the fifth time in two years. Sick of workarounds, was considering going back to RTG for enter/exit.

Checked this thread on a whim and woah! Great work, no more breaking for me on 10.16.9

@oliviercperrier
Copy link

oliviercperrier commented Apr 22, 2024

Still getting this with: 11.0.24

working fine as well for me on 9.0.2

Used that example: https://codesandbox.io/p/sandbox/framer-motion-accordion-qx958?file=%2Fpackage.json%3A9%2C6-9%2C22

with version 11.0.24, if you click multiple time on one element, the animation breaks

psychedelicious added a commit to invoke-ai/InvokeAI that referenced this issue May 6, 2024
There are a number of bugs with `framer-motion` that can result in sync issues with AnimatePresence and the conditionally rendered component.

You can see this if you rapidly click an accordion, occasionally it gets out of sync and is closed when it should be open.

This is a bigger problem with the viewer where the user may hold down the `z` key. It's trivial to get it to lock up.

For now, just remove the animation entirely.

Upstream issues for reference:
framer/motion#2023
framer/motion#2618
framer/motion#2554
hipsterusername pushed a commit to invoke-ai/InvokeAI that referenced this issue May 6, 2024
There are a number of bugs with `framer-motion` that can result in sync issues with AnimatePresence and the conditionally rendered component.

You can see this if you rapidly click an accordion, occasionally it gets out of sync and is closed when it should be open.

This is a bigger problem with the viewer where the user may hold down the `z` key. It's trivial to get it to lock up.

For now, just remove the animation entirely.

Upstream issues for reference:
framer/motion#2023
framer/motion#2618
framer/motion#2554
aeghn pushed a commit to aeghn/lettura that referenced this issue May 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.