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: In react 18, flashing effect appears at the end of exit phase before entering #816

Open
tranvansang opened this issue Apr 11, 2022 · 14 comments
Labels

Comments

@tranvansang
Copy link

tranvansang commented Apr 11, 2022

What is the current behavior?

When clicking the button
image

  • Phase 1: current element's opacity = 1
  • Phase 2: current element's opacity becomes 0 (with animation/transition)
  • Phase 3 (bug, react 18 ONLY): current element's opacity becomes 1 without animation/transition
  • Phase 4: current element disappears
  • Phase 5: next element opacity appears with opacity = 0
  • Phase 6: next element's opacity becomes 1 (with animation/transition)

What is the expected behavior?

Phase 3 shouldn't exist in react 18 renderer.

Could you provide a CodeSandbox demo reproducing the bug?

In demo's index.js, try changing legacyReact value to switch react 18 behav

2022-04-12.08-15-18.mp4

ior.

https://codesandbox.io/s/wonderful-glade-n0n92s?file=%2Fsrc%2Findex.js

@tranvansang tranvansang changed the title Bug: In react 18, flashing effect appears at the end of exiting phase before entering Bug: In react 18, flashing effect appears at the end of exit phase before entering Apr 11, 2022
@tranvansang tranvansang mentioned this issue Apr 12, 2022
1 task
@sotarules
Copy link

sotarules commented Jul 6, 2022

Friends, I have what I believe is the same issue, but in my case I'm animating a slide transition from right-to-left using 3D translate affecting the X value.

The "new" component enters from the right, while the "old" component simultaneously exits to the left.

What I'm seeing a perfectly executed transition until the very end, and then you see a brief flash of the old exiting component superimposed over the newly-entered component.

In theory, it should be impossible to see the old exited component at the end of the animation because the 3D translate rules, if processed as designed, would mean the exited component X value would place the old component 100% off screen. There should be no way to "see" the brief flash of the old exited component.

I am confident that the cause of this problem is the React 18 update batching, because I've been able to ameliorate the problem by doing the safeSetState using flushSync.

IMPORTANT: It is apparently only necessary to "wrap" the nextState value of exited with flushSync; all other nextState values (entering, entered, exiting) can be done using setState in the normal manner.

I believe that state-transitions exiting and exited are being erroneously combined into a single batch update by React 18.

This cannot be permitted, or the animation will be impacted.

The updates for exiting and exited must not be batched.

It is necessary to fully process the exiting state before setting exited state.

I hope this makes sense, this stuff is very complicated and hard to get your head around. Here is my fix.

 safeSetState(nextState, callback) {
      // This shouldn't be necessary, but there are weird race conditions with
      // setState callbacks and unmounting in testing, so always make sure that
      // we can cancel any pending setState callbacks after we unmount.
      callback = this.setNextCallback(callback)
      this.setStateConditionalFlushSync(nextState, callback)
  }

  /**
   * Prevent React 18 from batching updates.
   *
   * @param {object} nextState Next state object.
   * @param {function} callback Post-set callback.
   */
  setStateConditionalFlushSync(nextState, callback) {
      console.log(`Transition.js setStateConditionalFlushSync nextState=${JSON.stringify(nextState)}`)
      if (nextState?.status !== "exited") {
          console.log(`Transition.js setStateConditionalFlushSync *imperative* nextState=${JSON.stringify(nextState)}`)
          this.setState(nextState, callback)
          return
      }
      flushSync(() => {
          console.log(`Transition.js setStateConditionalFlushSync *flushSync* nextState=${JSON.stringify(nextState)}`)
          this.setState(nextState, callback)
      })
  }

@tranvansang
Copy link
Author

@sotarules

thanks!! so much, buddy.

I have patched the package using your fix, and now it WORKs LIKE A CHARM!!

@jcampuza
Copy link

jcampuza commented Aug 22, 2022

It doesn't seem to me this was ever fixed/released even though this is marked as completed?

@tranvansang
Copy link
Author

not fixed yet with the recently released version 4.4.5

here is the commit to fix c20af7c

@tranvansang tranvansang reopened this Oct 27, 2022
@asapach
Copy link

asapach commented Jan 27, 2023

not fixed yet with the recently released version 4.4.5

are there any plans to fix this and release a new version?

@koba04 koba04 added the bug label Jan 29, 2023
@ermolaev-unlimint
Copy link

This bug is still relevant.

1 similar comment
@ermolaev-unlimint
Copy link

This bug is still relevant.

@sergei-startsev
Copy link

@tranvansang is there a chance to release a new version with the fix?

@tranvansang
Copy link
Author

@tranvansang is there a chance to release a new version with the fix?

here it is

react-transition-group-react-18

or you can patch locally (tutorial)

@sergei-startsev
Copy link

@tranvansang I expected the existing package to be fixed rather than a new package being released.

@tranvansang
Copy link
Author

Im not the author of the package. Cannot publish any new version of it

@sergei-startsev
Copy link

I'm guessing there was confusion caused by the commit attached above. What prevented you from creating PR?

@asapach
Copy link

asapach commented Mar 17, 2023

@tranvansang I've found an alternate fix to your original issue: you can set animation-fill-mode: forwards to your styles. Here is the sandbox: https://codesandbox.io/s/bug-react-transition-group-with-react-18-forked-2m8993?file=/src/styles.css

@asapach
Copy link

asapach commented Mar 20, 2023

I've created a PR: #885

otomad added a commit to otomad/react-transition-group that referenced this issue Apr 19, 2024
reactjs#885

When running in React 18 concurrent mode some state updates are batched, which results in inconsistent timing of events compared to the legacy mode. For example when using animations, after animationend event fires, the onExited event is not fired immediately, so there is a brief period of time when animation is finished and the styles are reset back to normal, which may cause a flash or a jump. One of these scenarios is described in reactjs#816.

This change makes sure that the updates are performed synchronously, in order to make sure that events fire consistently.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants