WordPress.org

WordPress Developer Blog

Some very cool things can happen when you hit Enter in a block.

So you’re writing a list in the editor, and you hit Enter. And automatically, you get another bullet point!

Have you ever wondered how that happens? Would you like to make your own blocks do something like that, and not just with list items?

Well, that is what this article is here to help you with. It will probably make more sense to you if you’ve written some blocks before.

Three things usually happen.

When you’re editing a Gutenberg block, hitting Enter will usually do one of three things:

  1. Create a new block that matches the one you just wrote.
  2. Add a line break.
  3. Or add a new paragraph, because a paragraph is the default block.

The video below shows examples of all three.

When you hit enter to get a new block, you are using Gutenberg’s RichText component. The documentation, and people who write about WordPress, call this behavior splitting. While hitting enter at the end of a block adds a new block, hitting enter in the middle of the block splits it in two.

You need to use two functions for splitting, but there’s also a third function you’ll find handy to let users to reverse the split. That’s called merging.

  • onSplit – Used to create the new block (required)
  • onReplace – Used to add the new block – (required). Blocks should receive this as a prop already and often supplying this default will suffice.
  • onMerge – Used to stick blocks together (optional). Blocks should receive a mergeBlocks prop already which can be used.

You can pass these functions as props to the RichText components.

onSplit

onSplit receives the portion of the value getting split, and whether or not that portion comes after the original block (i.e., if whether the user is going to hit enter at the beginning of the content or not). It should return a new block, which will be called twice, once for each portion of the split.

Given a string of “Test” :

  • Hitting enter at the beginning means onSplit will produce '' and false on the first call, and then "Test” and true on the second call.
  • Hitting enter in the middle means onSplit will get ‘Te’ and true on the first call, and then ‘st’ and false on the second call.
  • Hitting enter at the end means onSplit will get ‘Test’ and true on the first call, and then '' and false on the second call.

In all cases, the value given should return a block. So a typical implementation will be inside a block edit component and look something like this:

<RichText
	onSplit={ ( value, isAfterOriginal ) => 
		createBlock( 'block/name', { ...attributes, text: value } );
	}
/>

attributes being the attributes prop passed into the edit component.

onReplace

onReplace receives an array that holds the blocks returned from onSplit. It also gets the index to select and the initial position.

It shouldn’t return anything; it should add the blocks to the editor. A simple implementation will be inside a block edit component and look something like this:

<RichText
	onReplace={ ( blocks, indexToSelect, initialPosition ) =>
		replaceBlocks( clientId, blocks, indexToSelect, initialPosition );
	}
/>

Where replaceBlocks comes from the block-editor store and clientId is the clientId of the current block.

The default onReplace prop does something a little like the above. Unless you need something wildly different, you might as well set it as a RichText prop and not bother to reinvent it.

onMerge

onMerge captures the direction of the merge. Like onReplace, it shouldn’t return anything, either. Instead, it should just find the blocks you want to merge in the editor—and merge them.

Your block should already receive a mergeBlocks prop. And you can certainly use that. But be aware that you’ll ultimately call the block’s merge function, which will pass the attributes from the two blocks you want to merge. So you will need to assign the block merge function in the block index.js file. That will typically look like this:

( firstAttribs, secondAttribs ) => { ...firstAttribs, text: firstAttribs.text + secondAttribs.text }

Further reading

  • Two blocks I found useful when I was researching this are the list-item block and the button block. They gave me solid examples of using all three functions in the real world.
  • You might want to see where onSplit and onReplace are called in context. Check out how rich-text/split-value.js calls `onSplit` and how rich-text/index.js calls onMerge.
  • And you’ll find that most of the logic from the default onMerge prop that is passed to blocks is found inside mergeBlocks.

With thanks to @bph and @marybaum for making this much more readable

Leave a Reply

Your email address will not be published. Required fields are marked *