0

Add WordPress Gutenberg Components Anywhere in DOM with createPortal

Posted in Javascript
Custom button added to Gutenberg top panel

Gutenberg may be the shiny new toy available for WordPress admin but it still feels very limiting at times – especially when trying to do things the developers don’t anticipate. I wanted to add an extra button at the top of the post edit page next to the Publish button before finding out there is no SlotFill for this but once again, React has us covered with a handy little helper createPortal. createPortal allows you to place a component anywhere in the DOM of your choosing – perfect for what I wanted to do here.

The Component

Without further adieu, component code below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
 * WordPress dependencies
 */
import { Button } from '@wordpress/components';
import { compose } from '@wordpress/compose';
import { createPortal, useEffect, useState } from '@wordpress/element';
import { withSelect } from '@wordpress/data';
 
const TopBtn = ( { postPreviewButton } ) => {
	// Empty div element our button will be placed into
	// This div will be inserted before the Publish button with useEffect()
	const [ container ] = useState( document.createElement( 'div' ) );
 
	// The button submit handler
	const onSubmit = () => {
		console.log( 'Button pressed!' );
	};
 
	// Do the actual placing/removing of the button in the DOM
	useEffect( () => {
		// Ensure we have a Preview button to place next to
		if ( ! postPreviewButton ) {
			return;
		}
 
		// Insert our button immediately after the post preview button.
		postPreviewButton.parentNode.insertBefore(
			container,
			postPreviewButton.nextSibling
		);
		// Remove our button on component unload
		return () => {
			postPreviewButton.parentNode.removeChild( container );
		};
	}, [ postPreviewButton ] );
 
	// Use createPortal to place our component into the <div/> container
	return createPortal(
		<>
			<Button
				isSecondary
				showTooltip={ true }
				label="My Custom Button"
				onClick={ onSubmit }
			>
				My Btn
			</Button>
			<span>&nbsp;</span>
		</>,
		container
	);
};
 
export default compose( [
	withSelect( () => {
		return {
			// Provide the Post preview button element so we'll know where to
			// place our component
			postPreviewButton: document.querySelector( '.editor-post-preview' ),
		};
	} ),
] )( TopBtn );

This setup is quite flexible allowing you to add components pretty much anywhere on the page you like. I’ve commented the component as best I could. If you have any questions feel free to ask in the comments section below.