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: CSS animations jump between Suspense children and fallback #23217

Closed
bug-brain opened this issue Jan 31, 2022 · 5 comments
Closed

Bug: CSS animations jump between Suspense children and fallback #23217

bug-brain opened this issue Jan 31, 2022 · 5 comments
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug

Comments

@bug-brain
Copy link

bug-brain commented Jan 31, 2022

React version: 17.0.2, 18.0.0-rc0

Steps To Reproduce

  1. Have a Suspense with an animated element as fallback, in this case a div.fade that has has an animation duration of 5s.
  2. Make its children the same if it is not suspended.
  3. Switch between states, in this case done with an interval of 2s.
  4. Observe that the elements animation is reset on every state change.

Link to code example: https://codesandbox.io/s/rough-water-zxc2m

The current behavior

The CSS animation resets probably because the DOM element is replaced. Every 2s the animation starts from the beginning.

The expected behavior

The Suspense's children and fallback are reconciled and the DOM remains unmodified so that the animation can complete. In other words, I think it should work like this example: https://codesandbox.io/s/eager-river-ww04i. This would make it much easier to build smooth loading indicators using a mix of Suspense and conditional rendering.

@bug-brain bug-brain added the Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug label Jan 31, 2022
@randomdevice
Copy link

randomdevice commented Jan 31, 2022

From what I ran, useEffect() is run once but utilizing setInterval causes the Loading component to continually load. I switched it out with setTimeout instead. The Promise is thrown but doesn't load anything on the DOM since it doesn't compile as a visual component.

I switched out the throwPromise component with one that simply loaded a solid red div to better visualize the results. I also added animation-iteration-count: infinite to the .loading class in your CSS so that useEffect didn't need to be used to constantly reload the Loading component.

I hope this helped somewhat.

The result:
https://eo7zf.csb.app/

Sandbox:
https://codesandbox.io/s/affectionate-bird-eo7zf?file=/src/styles.css

Here is the React code:

import { Suspense, useEffect, useState } from "react";
import "./styles.css";

function Loading() {
  return <div className="loading" />;
}

const ADiv = () => <div className="cube"></div>;

export default function App() {
  const [suspended, setSuspended] = useState(false);
  useEffect(() => {
    setTimeout(() => setSuspended((s) => (s ? s : !s)), 4000);
  }, []);
  return (
    <Suspense fallback={<Loading />}>
      {console.log(suspended)}
      {suspended ? <ADiv /> : <Loading />}
    </Suspense>
  );
}

and CSS

@keyframes fade {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

.loading {
  width: 5em;
  height: 5em;
  background-color: currentColor;
  animation: fade 1s forwards;
  animation-iteration-count: infinite;
}

.cube {
  width: 5em;
  height: 5em;
  background-color: red;
}

@bug-brain
Copy link
Author

Thank you for looking into this issue but I'm afraid your answer does not help me. I have added a bit more details to the description and another example that shows what I thought should happen. I hope that clarifies the problem a bit.

@gaearon
Copy link
Collaborator

gaearon commented Feb 3, 2022

The Suspense's children and fallback are reconciled

That's not something we plan to allow. However, you're right animating from/to fallback is an important use case. I don't think we have a great solution for it today but it will be a part of a future effort to add first-class animations to React. See reactjs/rfcs#154 (comment) for thoughts on that.

@bug-brain
Copy link
Author

Thanks for providing the link to your comment. It was rather useful for understanding the problem. I am closing this issue because to my knowledge the reconciliation of children and fallback would have been the only solution.

@bug-brain
Copy link
Author

The following setup is a workaround for this issue. Wrap the relevant tree into a loading boundary component that tracks the loading state, has a context to change it and renders the loading indicator in addition to the children (which should always render to nothing when loading). Instead of using the indicator as a fallback use a toggle component that changes the state fo the context when entering or leaving using a layout effect but renders nothing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug
Projects
None yet
Development

No branches or pull requests

3 participants