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

Image Block is replaced as a Youtube block after dragging multiple blocks outside their parent #61778

Open
jeflopodev opened this issue May 19, 2024 · 17 comments
Labels
[Block] Image Affects the Image Block [Feature] Drag and Drop Drag and drop functionality when working with blocks [Type] Bug An existing feature does not function as intended

Comments

@jeflopodev
Copy link

jeflopodev commented May 19, 2024

Description

I have created blank theme with the Create Block Theme Plugin.
I have created a post and I added the following blocks structure:

  • Group
    • Group
      • Heading
      • Paragraph
      • Image
      • Youtube (I have created this block by pasting the video URL in the post editor)

Then I have selected the Heading, Paragraph, Image and Youtube blocks and dragged them outside their parent.

The image block got replaced by another instance of a youtube block, with the same video. Doesn't matter if I drag it one level upper or at root level.

Seems to happen only when an Image block is sibling of the youtube block. I can't replicate this behavior with the paragraph or heading blocks.

After dragging the blocks, and trying to press Undo I need to spam it multiple times till I get to the state before dragging the blocks. Doing single clicks to it doesn't work.

I also tried with the TT4 theme. And the exact same happens.

Step-by-step reproduction instructions

  1. Create a blank theme using Create Block Theme (or use the TT4 theme)
  2. Create a new post
  3. Create a Group Block within another Group Block that contains at least one image block and a Youtube block (siblings).
  4. Create a multiple selection that comprehends at least the image and youtube blocks.
  5. Drag those blocks outside of it's parent.
  6. Check if the image block got converted to a Youtube Block
  7. Check if single clicks to the undo button doesn't work
  8. Check if you have to spam click the undo button for it to work again

Screenshots, screen recording, code snippet

gg.mp4

Environment info

  • WP 5.3.0
  • Gutenberg 18.3.0
  • Blank theme created with the Create Block Theme Plugin
  • Chrome v124.0.6367.209
  • Desktop Windows 11 23H2

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

@jeflopodev jeflopodev added the [Type] Bug An existing feature does not function as intended label May 19, 2024
@jordesign jordesign added [Block] Image Affects the Image Block Needs Testing Needs further testing to be confirmed. labels May 20, 2024
@Mamaduka
Copy link
Member

Mamaduka commented May 20, 2024

I can also reproduce this on the Gutenberg trunk. I can reproduce the issue with WP 6.5 and no Gutenberg plugin.

The order in which Image and Embed blocks are placed also doesn't matter.

What's happening

  • After the dragging image block receives Embed block URL as new url attributes. I'm not sure yet why.
  • The YT link can be rendered as an image.
  • Block calls onImageError and converts it into an embed block.

@Mamaduka Mamaduka added [Feature] Drag and Drop Drag and drop functionality when working with blocks and removed Needs Testing Needs further testing to be confirmed. labels May 20, 2024
@jsnajdr
Copy link
Member

jsnajdr commented May 20, 2024

I can reproduce this. When both an Image and Embed block are selected and dragged to a new location, the React UI for both will be unmounted and re-mounted at the new location.

The Embed block has this effect with a setAttributes call that updates the Embed block's attributes with data from the embed preview:

useEffect( () => {
if ( preview && ! isEditingURL ) {
// When obtaining an incoming preview,
// we set the attributes derived from the preview data.
const mergedAttributes = getMergedAttributes();
setAttributes( mergedAttributes );
if ( onReplace ) {
const upgradedBlock = createUpgradedEmbedBlock(
props,
mergedAttributes
);
if ( upgradedBlock ) {
onReplace( upgradedBlock );
}
}
}
}, [ preview, isEditingURL ] );

But this setAttributes function doesn't just update the Embed block, it updates all selected blocks in a multi-selection:

setAttributes( newAttributes ) {
const { getMultiSelectedBlockClientIds } =
registry.select( blockEditorStore );
const multiSelectedBlockClientIds =
getMultiSelectedBlockClientIds();
const { clientId } = ownProps;
const clientIds = multiSelectedBlockClientIds.length
? multiSelectedBlockClientIds
: [ clientId ];
updateBlockAttributes( clientIds, newAttributes );

Because both the Embed and Image blocks are selected, the Image block's url attribute is set to a YouTube URL. This URL fails to load as an image, and that triggers an automatic transform of the Image to YouTube Embed:

function onImageError() {
// Check if there's an embed block that handles this URL, e.g., instagram URL.
// See: https://github.com/WordPress/gutenberg/pull/11472
const embedBlock = createUpgradedEmbedBlock( { attributes: { url } } );
if ( undefined !== embedBlock ) {
onReplace( embedBlock );
}
}

@youknowriad @ellatrix This behavior where setAttributes updates all selected block has been there for years, and yet it seems horribly wrong? The documentation for the block Edit function says quite clearly that I can do this:

function Edit( { attributes, setAttributes } ) {
  <button onClick={ () => setAttributes( { foo: 'bar' } ) }>set</button>
}

and the intent is quite obviously to set the attributes of exactly one block. And yet it's setting foo to bar on all multi-selected blocks.

@Mamaduka
Copy link
Member

Thanks for finding the attribute update cause, @jsnajdr! I should've guessed it was a multi-selected block attribute update.

Jarda, do you plan to work on a fix? I have an idea for an Embed block "hotfix" and happy to push it.

P.S. This proves again that "on-mount effects" should be avoided in blocks or used very carefully.

@jsnajdr
Copy link
Member

jsnajdr commented May 20, 2024

This proves again that "on-mount effects" should be avoided in blocks or used very carefully.

I think that the Embed block doesn't do anything wrong. It simply wants to update its own attributes, that's legit. The problem is that the setAttributes function updates all selected blocks. There's certainly also some good reason why that was added. We need to discuss that and figure out what is the right fix.

@ellatrix
Copy link
Member

Should we add some early returns in that setAttributes action? It should only update a multi-selection if the blocks are of the same type. I believe there are checks on render, but not in that action.

@Mamaduka
Copy link
Member

Mamaduka commented May 20, 2024

Should we add some early returns in that setAttributes action? It should only update a multi-selection if the blocks are of the same type. I believe there are checks on render, but not in that action.

That will only partially solve the issue. If you drag multiple embed blocks with different URLs, they'll have the same URL after the dragging.

Edit: The original logic was introduced in #22470.

@jsnajdr
Copy link
Member

jsnajdr commented May 20, 2024

It should only update a multi-selection if the blocks are of the same type.

This is still wrong, because if I select and drag two adjacent YouTube Embed blocks, they will update each other's URLs.

The question is more like: why is setAttributes looking at the multi-selection at all? Why isn't it updating just the one clientId that it is bound to? What's the use case?

@jsnajdr
Copy link
Member

jsnajdr commented May 20, 2024

What's the use case?

I see it now: the use case is that you select two paragraph blocks, and then in the sidebar edit the text color:

Screenshot 2024-05-20 at 11 10 35

This will update the attributes of both blocks.

But it looks like a bad idea to use setAttributes for that. The controls should be able to explicitly choose if they want to update just one block, or multiple ones.

@Mamaduka
Copy link
Member

Mamaduka commented May 20, 2024

I don't think we can just remove current behavior, as it would be considered a breaking change.

FYI, updating values for multiple blocks of the same type also has issue #51609.

@Mamaduka
Copy link
Member

@ellatrix, @jsnajdr, what do you think about creating a separate issue to continue discussions regarding solving potential bugs with current setAttributes behavior?

The Embed block's "on-mount effect" bug can be solved separately.

@jeflopodev
Copy link
Author

jeflopodev commented May 20, 2024

Oh I'm very happy to see everyone's response in my bug report. Unfortunately I yet lack the knowledge to contribute beyond filing a report. I would be completely lost creating pull requests, committing changes etc. I guess no further actions are required from me, is that correct ?

@Mamaduka
Copy link
Member

Mamaduka commented May 20, 2024

Thanks for reporting the bug, @jeflopodev!

That's right; you don't need to do anything else.

P.S. You might get a notification to link your GitHub account with WP.org when the pull request is merged so that you get contribution credit.

@cbirdsong
Copy link

But it looks like a bad idea to use setAttributes for that. The controls should be able to explicitly choose if they want to update just one block, or multiple ones.

This may be the source of the bug in #41260?

@jsnajdr
Copy link
Member

jsnajdr commented May 21, 2024

This may be the source of the bug in #41260?

@cbirdsong Yes, this bug has the same underlying cause. The Layout control is not really aware that it's editing layout for multiple blocks. It receives the attributes of one block, the first selected one. It reads .layout, which is:

{
  type: 'flex',
  justifyContent: 'right',
  flexWrap: 'wrap',
}

When you disable the "Allow to wrap" toggle, it changes the flexWrap field and calls:

setAttributes( {
  layout: {
    type: 'flex',
    justifyContent: 'right',
    flexWrap: 'nowrap',
  }
} )

Here setAttributes will update the attributes of all selected blocks, and that's how the justifyContent: 'right' of the first selected block is applied to all others.

I don't think we can just remove current behavior, as it would be considered a breaking change.

@Mamaduka This will be tough, because the current API behavior is seriously broken and there are many cases that are handled incorrectly. We can't keep it that way forever.

If setAttributes is fixed to always modify just one block, and there is a new API introduced for block controls to modify multiple blocks at once, how will that break existing blocks? Instead of modifying all selected blocks, they will modify only the first. They will need to be patched if they want to modify multiple blocks. That's not awful. When balanced against the value of having a well-behaved stable API, it might turn out to be acceptable.

@Mamaduka
Copy link
Member

@jsnajdr, that's a good point. I doubt many are aware of the current setAttributes behavior anyway. A dev note with a migration path should help mitigate any breakage.

What do you think of my proposal to create a separate issue for the general setAttributes fix?

P.S. The updateBlockAttributes has an uniqueByBlock flag/argument, which I believe was an attempt to fix this behavior. Currently used only by the Gallery block. See: #29099.

@ellatrix
Copy link
Member

ellatrix commented May 21, 2024

Sure, we can create a new API to modify multiple blocks. This was a tradeoff we made when adding the capability to modify multiple blocks. Unfortunately all controls are nested within block edit functions. If we do something like Block API: try separate controls registration, it will also fix this problem, because we can just get all the controls for a block type.

@jsnajdr
Copy link
Member

jsnajdr commented May 21, 2024

what do you think about creating a separate issue to continue discussions regarding solving potential bugs with current setAttributes behavior?

Maybe #41260 could be the "canonical" bug for this? It is more directly related to the setAttributes behavior.

The effects in the Embed block can be tidied up: currently setAttributes is called even when no attributes are going to change -- it should be conditional. That alone will fix this bug.

The updateBlockAttributes has an uniqueByBlock flag/argument, which I believe was an attempt to fix this behavior.

It seems to me only weakly related. It's used by the Gallery block to loop through the child blocks, Images, and set attributes on them. It's a shortcut for registry.batch + loop + updateBlockAttributes (single).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Block] Image Affects the Image Block [Feature] Drag and Drop Drag and drop functionality when working with blocks [Type] Bug An existing feature does not function as intended
Projects
None yet
Development

No branches or pull requests

6 participants