WordPress / gutenberg

The Block Editor project for WordPress and beyond. Plugin is available from the official repository.
https://wordpress.org/gutenberg/
Other
10.46k stars 4.18k forks source link

useBlockProps rerender block multiple times #56664

Open wpsoul opened 10 months ago

wpsoul commented 10 months ago

Description

I implemented API 3 to blocks and see big degradation of performance. After some research, I found that useBlockProps rerender block multiple times. Also it rerenders blocks when it's in View.

Step-by-step reproduction instructions

  1. Add console.log in edit function
  2. Add useBlockProps and check console. I see 4 times execution, also when I scroll down/up, I see execution when block is in view
  3. Now, remove useBlockProps. Console executed 1 time only

Screenshots, screen recording, code snippet

https://github.com/WordPress/gutenberg/assets/4378263/d082f4ac-0791-4cf5-a4bd-77059b9f8af7

Environment info

6.4 Wordpress

Please confirm that you have searched existing issues in the repo.

Yes

Please confirm that you have tested with all plugins deactivated except Gutenberg.

Yes

wpsoul commented 10 months ago

After more testing, I simplified block and it renders 2 times but still rerender on view even if edit function has only "div" and nothing more. How to prevent double render and rerender on view?

Mamaduka commented 10 months ago

This isn't a bug; re-rendering in React is usually fast unless a component performs some heavy/slow computations.

The useBlockProps grabs required information for a block and keeps track of various block states, which can trigger a re-render so that the block displays the latest data.

wpsoul commented 10 months ago

@Mamaduka I can't agree with this. There is no reason to make double render the same block on init (I don't see this without useBlockProps) and there is no reason to rerender block when it's in view because block doesn't change state on this trigger. Imagine that page can have hundreds of blocks and just scrolling will make rerender them with slow performance. I will continue tests to find where this strange re render is triggered

wpsoul commented 10 months ago

So, is there any solution to remove rerender on View? I think it's using Intersection Observer but I don't see reason for blocks to rerender when they are in view

Mamaduka commented 10 months ago

cc @WordPress/gutenberg-core

t-hamano commented 10 months ago

There is useIntersectionObserver in the ueBlockProps hook that seems to cause re-rendering when scrolled. However, reading #30995, it looks like it was introduced intentionally to solve some problem.

youknowriad commented 10 months ago

the intersection observer allows us to enable "async mode" for the blocks that are out of view. Meaning the editor will be more responsive because these out of view blocks will have "async mode" active.

So if this re-rendering is inevitable because of the intersection observer (we need to confirm that this assumption), then it's fine because the gains from async mode are huge compared to a single re-rendering.

wpsoul commented 10 months ago

@youknowriad this can have a sense if observer is working only once. What happens now on loading

  1. All blocks are rendering (even those which are not in view)
  2. User scroll down, they are rerendering. Why? Blocks already rendered, they didn't change states, no need to rerender them again
  3. When user scroll down and scroll up, blocks are again rerendered.

So, Observer doesn't solve anything, it's just adding new rerender on top of existed

youknowriad commented 10 months ago

@wpsoul I can guarantee that you're wrong. Try disabling the observer and type in the editor and you'll see what I'm talking about. There might be a couple initial re-rendering happening and we could try to debug that to see what we can do there. But saying that the observer is not doing anything is not true.

Typing metric right now is 30ms in codevitals.run without it, it could be x10 slower

wpsoul commented 10 months ago

@youknowriad I already showed this on video. Old API doesn't re rerender blocks. it's just rendering 1 time as it should. Can you explain me how constant multiple rerendering of block can improve performance? What is the reason of rerender blocks if they are already rendered and they are not changed?

youknowriad commented 10 months ago

When a block goes out of view it becomes "async" meaning that if you make a change to another block (like typing in another block), your block will run its selectors (any useSelect call and a lot of hooks and things like that) asynchronously, when the browser is idle.

So if you have a post with 1000 blocks and 980 are out of view, these blocks will be async and the visible ones will be synchronous (call selectors to check if they should update on each change).

The downside, is that to do that, we need to switch the "isAsync" for all blocks when they go "out of view" or "in view". This is a very small price to pay compared to rendering 1000 blocks synchronously causing huge lags for the user when typing.

wpsoul commented 10 months ago

if so, then why my blocks on API 1 are fine to load and browse and on API 3 - very laggy? interesting, that this is mostly related to situations when I have many inner blocks. For example, if I add 300 blocks on page - it's fine, a bit slow but not critical. But when I start to add groups, rows and many nested elements, this is so slow, almost not usable. And inside FSE it's even worse, my hosting usually goes down when I try to save FSE page with many elements.

talldan commented 10 months ago

I already showed this on video.

What you're doing on the video doesn't seem quite right as you're just adding/removing useBlockProps without changing the api version. If you change the api version of your block back to 1 and remove useBlockProps then I'd expect it to also re-render when going out of view.

The code here shows how the compatibility works: https://github.com/WordPress/gutenberg/blob/2642697ff65f6f011044f8939f9f7ed70af2945c/packages/block-editor/src/components/block-list/block.js#L211-L215

When using api version 1, the else branch will wrap your block for you and call useBlockProps so it'll work the same: https://github.com/WordPress/gutenberg/blob/2642697ff65f6f011044f8939f9f7ed70af2945c/packages/block-editor/src/components/block-list/block.js#L66-L72

A v2/3 block without useBlockProps may not work correctly.