-
-
Notifications
You must be signed in to change notification settings - Fork 855
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] Animations do not resume after components resume after suspense #2269
Comments
@Pinpickle Hi, it seems the following SuspenseTest component works, it uses a ref to store the promise instead of a state, not forcing any re-render when setting or resetting.
|
@gurkerl83 you're right that this no longer causes the animation to become stuck. But that's because the component never suspends, so it just means it's no longer running into the bug in Motion. In this example you can technically create the promise completely outside of React, but this was just a contrived example to get to causing suspense as fast as possible. A more useful example could be using const LazyDisplayNumber2 = lazy(async () => {
// artificial network delay
await new Promise((r) => setTimeout(r, 1000));
return import("./DisplayNumber2");
});
const SuspenseTest = ({ shouldSuspend }: { shouldSuspend: boolean }) => {
const [isDisplaying, setIsDisplaying] = useState(false);
useEffect(() => {
// We need to make sure the component manages to mount
// and start the animation before triggering suspense.
setIsDisplaying(true);
}, []);
return (
<div style={{ display: "flex", justifyContent: "center" }}>
<motion.div
style={{ fontWeight: "bold" }}
initial={{ opacity: 0.2, scale: 1 }}
animate={{ opacity: 1, scale: 2 }}
transition={{ duration: 2 }}
>
{isDisplaying && shouldSuspend ? <LazyDisplayNumber2 /> : "1"}
</motion.div>
</div>
);
}; This has the same problem of "getting stuck" Codesandbox (I couldn't get Stackblitz to work this time π): https://codesandbox.io/s/vigorous-lamarr-sjssxd?file=/src/App.tsx |
You are absolutely right, the promise is never thrown, at least not when no state change gets triggered during the promise gets created and resolves in the timeout. Working with Suspense often requires the usage of the useTransition hook. Props put into a component wrapped by a suspense boundary require doing a state update starting a transition. But in the second example state update is executed within. Maybe lazy automatically adds the Suspense component over LazyDisplayNumber2, then this makes sense. From the new example is this what you want to achieve? You need useTransition from react. Suspense and Transition has improved over the last year dramatically I recommend installing a 18.3 canary version of react and react-dom.
|
@gurkerl83 there are plenty of workarounds to make sure a component doesn't suspend in isolated cases. Regardless, I'm pretty sure this is a bug in Framer Motion and (in my opinion), animations should still work even if they've been interrupted with suspense. |
The technical reason behind this looks like when the component throws, the |
@mattgperry thanks for investigating! Any suggestions for workarounds in the meantime? Is there a way to force an animation to restart? |
@mattgperry @Pinpickle Found the issue, with the ref problem, please the the comments made in the component.
The final hook looks like this
|
While framer-motion can execute the animation with the current changes, it doesn't truly resume the animation. Specifying Suspense outside of a motion component doesn't seem advantageous. Without modifying any sources, the same outcome can be achieved by integrating Suspense directly within the component and subsequently using lazy loading. By doing this, the promise or "thenable" throw is confined specifically to this Suspense boundary, ensuring that framer-motion remains uninterrupted. For illustrative purposes, please avoid using an external Suspense component in this example.
|
The provided PR #2283 is certainly not the right approach to fix this problem. |
@mattgperry I don't suppose you've had any time to further investigate this? The bug is particularly problematic with AnimatePresence where a suspense can prevent an element from unmounting successfully. |
Thanks to the double invocation of |
@mattgperry are you saying that this should work out-of-the-box in React 19, or the required fixes for double-invocation in strict mode will also fix this? Going by the React 19 docs, I'm assuming it's the latter?
|
Yeah the latter. I'm working on a PR that will merge in the logic changes I made for React 19 so it could be fixed before then. |
Ah apologises this doesn't seem to have fixed it. |
@mattgperry yeah unfortunately it looks like the repro is still failing in 11.3.2 For what it's worth, another instance in the app I'm working on where suspense stopped an animation isn't happening anymore. Unfortunately I'm not sure what the difference between that case and this case is. |
1. Read the FAQs π
β
2. Describe the bug
Animations that are interrupted with suspense have weird behaviour. Sometimes they will freeze in place. Sometimes they will freeze at the beginning. Sometimes they will complete.
3. IMPORTANT: Provide a CodeSandbox reproduction of the bug
I made a StackBlitz, hopefully that's OK: https://stackblitz.com/edit/vitejs-vite-uhm4h9?file=src%2FApp.tsx
4. Steps to reproduce
Steps to reproduce the behavior:
5. Expected behavior
For the animation to complete eventually once the suspended component re-mounts. Any one of these would be reasonable IMO:
There would be different behaviours for repeating animations and unmounting animations.
6. Video or screenshots
CleanShot.2023-08-01.at.14.25.16.png.mp4
7. Environment details
Chrome on macOS (M1)
Version 114.0.5735.198 (Official Build) (arm64)
The text was updated successfully, but these errors were encountered: