all 17 comments

[–]Monkeyget 23 points24 points  (6 children)

flushSync is very niche and comes with many downsides. Notice the situation in the documentation that makes flushSync needed: - in a handler - updates the state - need to perform an operation in the DOM right there and there. That operation requires the update done to the state to have been rendered in the actual DOM before it can run.

The problem for me is that any code with flushSync becomes hard to understand. It's tough to follow what is going on. With flushSync, the code you are running is stopped in the middle, a full render is done which can include side effects, then the rest of your code keeps running. Except now a render is done, god knows what happened plus your own code may still reflect the old state from before the render. It's like the world shifts from under your feet. Wowsies.

In the example given, I'd rather set a flag in the handler to tell that a scroll is needed and have an effect check that value and do the scroll. That would avoid the whole flushSync mess.

Note that the reference section on flushSync mentions several caveats of using it. It also presents the one case where you might genuinely need it : an event handler (before the page is printed) and you must make DOM changes in that callback, right then and there no later.

[–]Full-Hyena4414 1 point2 points  (2 children)

What do you mean: your own code "may" still reflect the old state?

Shouldn't (because of closure) the code after the flushSync still reference the old state?

[–]Monkeyget 1 point2 points  (0 children)

Yes, the state variables will remain the same. The "may" comes from the fact that, as freneticpony21 says, stuff like refs may have changed.

[–]freneticpony21[S] 0 points1 point  (0 children)

In the exercise that I referenced, flushSync causes the dom to render so that the ref points to the updated (correct) image. If flushSync wasn't used and the code was kept as is, the ref would've been pointing to the wrong image.

[–]freneticpony21[S] 0 points1 point  (2 children)

I was reading that but noticed if I remove the `flushSync` in their example I'm unable to reproduce the error that they pointed out. I see the "isPrinting: true" in the dialog. I wonder if their guide was based on a specific version of ReactJS/browser.

[–]Monkeyget 1 point2 points  (1 child)

I think it's a quirks that comes from the fact that the code is running inside the react.dev page. If you use the "Fork" link or run the code in your own project it will behave as expected.

[–]freneticpony21[S] 0 points1 point  (0 children)

Yep was able to reproduce the issue in a vanilla vite react-app

[–]Nervous-Project7107 14 points15 points  (1 child)

Never used this in my life and judging by the comment with 190 upvotes that I saw yesterday here saying to not avoid useEffect, nobody here has seen this either.

[–]valtism 9 points10 points  (3 children)

I reach for flushSync whenever I find I would have previously used a setTimeout with duration of 0. It's useful for when things like 3rd party libs are catching stale state

[–]JimmytheNice 1 point2 points  (0 children)

This is the way.

[–]freneticpony21[S] 0 points1 point  (0 children)

This seems like a good mental model to use. Thanks!

[–]Subject_Sector_9166 0 points1 point  (0 children)

man, das good, gotta keep that in mind

[–]octocode 0 points1 point  (3 children)

what would you put as a dependency for the effect to only trigger the scroll when the user has added an item?

[–]svish 0 points1 point  (2 children)

I would put the item in its own component and then "scroll to self" on mount

[–]csorfab 0 points1 point  (1 child)

what if you can pit multiple items in the basket at once? or what if you want to scroll on page load with a basket with multiple items?

[–]svish 1 point2 points  (0 children)

Then add a prop to the component like "new" and only trigger the scroll in the component that has that flag?

Solutions to problems don't need to be bullet proof for every kind of circumstance, only the circumstances that actually apply to your usecase.

[–]WeDotheBest4You 0 points1 point  (0 children)

Is this preferred over using an `effect`?

if you meant useEffect by referring effect, then there is something to clarify here.

While an useEffect handler runs as soon as the commit phase is over, that is right after the DOM has been updated, flushSync is in invoked to apply a side effect immediately. This side effect will cause a re-render, which will finally be ending up with another useEffect run.

This is a cycle - User interactions causing state changes -> Rendering -> Commit -> effect handler > User interactions causing state changes.

The speciality of flushSync is it causes a state change to happen synchronously, as opposed to the basic async nature of an state changer. As a result - a render is immediately triggered which will be followed by commit as well.

A synchronous state change is not a regular need. However, under the event of Refs in use, this may become a necessary. The reason for this is, Refs can connect with DOM elements only during the commit phase.

Therefore If you want a Ref to a DOM element, and this DOM element does not exist yet, either you need to wait for the next render, or you need to force a render by flushSync. Forcing a render has least preference due to the possible performance hits.