Yellow flowers blooming in a pile of dry leaves

Creating Reusable React Hooks For The WordPress Block Editor or Whatever

The new React documentation site is live and looks great. This section on creating custom hooks to encapsulate logic that needs to be used in multiple places in a React application is quite good. This is an important strategy to learn, and can help a lot when developing multiple blocks for a WordPress site.

In this short post, I am going to show a few examples of custom hooks I’ve made for plugins I’m working on. I hope this will help you learn to create reusable custom hooks.

What Is A React Hook And Why Do I Need It?

So, you might be wondering why you would create a custom React hook in a WordPress plugin anyway? It’s all about reusability and keeping your code organized.

When you’re building a WordPress plugin with React, you’re likely going to have a lot of components that need to share state or functionality. Instead of duplicating code or passing props down through multiple levels of components, you can create a custom hook that encapsulates that state or functionality and make it available to any component that needs it.

For example, let’s say you’re building a plugin that has a form with multiple input fields. You could create a custom hook that handles the form state and validation logic, and then use that hook in each of the input field components. This way, you don’t have to repeat the same code in each component, and you can easily update the form logic in one place if needed.

Also, creating custom hooks can help keep your code organized and easier to maintain. You can group related functionality together in a single hook, and then import that hook wherever it’s needed. This can make it easier to understand how your code works and make changes without introducing new and exciting bugs.

Example React Hooks For The WordPress Block Editor

Using React Hooks To Get Posts or Pages In The WordPress Block Editor

It is common to populate data in the editor with posts, using the WordPress data package. Here is a simple hook “usePages” that makes it easy to get recent posts in an array:


import { useSelect } from '@wordpress/data';
const usePages = () => {
	const pages = useSelect((select) => {
	 	let data = select('core').getEntityRecords('postType', 'pages', {
			per_page: 50,
		});
		if( ! data ) {
			return [];
		}
		return data;
	});
	return {pages};
};

Why do this? Mainly so I don’t need to remember how this works every time I need it. Also, I’m likely need to do some validation, for example dealing with the the case when I get no results.

A super common use for a hook like this is to populate select options in the SelectControl. Here is a hook that gets pages and turns them into an array of options:

import { useSelect } from '@wordpress/data';
const usePagesAsOptions = () => {
	const options = useSelect((select) => {
	 	let data = select('core').getEntityRecords('postType', 'pages', {
			per_page: 50,
		});
		if( ! data ) {
			return [];
		}
		return data.map((page) => {
			return {
				label: page.title.rendered,
				value: page.id,
			};
		});
	});
	return {options};
};

Start Simple, Add More Functionality Later

Once you have a hook that works, you can refactor it to do more, as needed. I like to start with something simple and then add more flexiblility. For example, lets make this work with any post type:

import { useSelect } from '@wordpress/data';
const usePostTypeOptions = ({postType) => {
	const options = useSelect((select) => {
	 	let data = select('core').getEntityRecords('postType', postType, {
			per_page: 50,
		});
		if( ! data ) {
			return [];
		}
		return data.map((post) => {
			return {
				label: post.title.rendered,
				value: post.id,
			};
		});
	});
	return {options};
};

Thanks to a few small changes, this works for any post type. Nice.

Getting The Parent Block Of The Selected Block

Ok, last hook. This one tells you the clientId of the parent block of an inner block. This is helpful when creating nested blocks.

import { useSelect } from '@wordpress/data';

/**
 * Get attributes of the parent block
 *
 * @param  clientId
 */
export const useParentBlockAttributes = (clientId) => {
	const parentClientId = select('core/block-editor').getBlockHierarchyRootClientId(clientId);
	return select('core/block-editor').getBlockAttributes(parentClientId);
};

Hook For Managing Plugin Settings

This is a hook in my AI-writing plugin. You can see the source here and where it is used here. This hook encapsulates two related functions: saving settings and indicating the status of the save. In user interface development, these are highly coupled and things get confusing if the logic isn’t coupled, so that’s how I do it.

import React from 'react';
import apiFetch from '@wordpress/api-fetch';

//Function for saving settings
const saveSettings = async ( values ) => {
	const r = await apiFetch( {
		path: '/ufo-ai/v1/settings',
		method: 'POST',
		data: values,
	} ).then( ( res ) => {
		return res;
	} );
	return { update: r };
};

/**
 * Hook for saving settings
 *
 * @returns {Object} {saveSettings: function, isSaving: boolean, hasSaved: boolean}
 */
export const useSettings = () => {
	const [ isSaving, setIsSaving ] = React.useState( false );
	const [ hasSaved, setHasSaved ] = React.useState( false );

	//Reset the isSaving state after 2 seconds
	React.useEffect( () => {
		if ( hasSaved ) {
			const timer = setTimeout( () => {
				setIsSaving( false );
			}, 2000 );
			return () => clearTimeout( timer );
		}
	}, [ hasSaved ] );
	return {
		saveSettings: ( values ) => {
			setIsSaving( true );
			saveSettings( values ).then( () => {
				setIsSaving( false );
				setHasSaved( true );
			} );
		},
		isSaving,
		hasSaved
	};
};
export default useSettings;

While saving settings is part of the settings screen, using settings is part of many screens. That’s why I created one hook to read each setting or setting group. This one checks if there is an API key saved and if it is valid:

import React from 'react';
import apiFetch from '@wordpress/api-fetch';
const checkConnection = async () => {
	const is = await apiFetch( {
		path: '/ufo-ai/v1/connected',
		method: 'GET',
	} )
		.then( ( res ) => {
			if ( res ) {
				return res.connected;
			}
			return false;
		} )
		.catch( () => {
			return false;
		} );
	return is;
};

export const useConnectionCheck = () => {
	const [ connected, setConnected ] = React.useState( false );
	const [ checking, setChecking ] = React.useState( false );
	React.useEffect( () => {
		setChecking( true );
		checkConnection().then( ( is ) => {
			setConnected( is );

			setChecking( false );
		} );
	}, [] );
	return { connected, isCheckingConnection: checking };
};
export default checkConnection;

Make Some Hooks!

WordPress developers love hooks. It has always been important to learn about our old friends the PHP hooks, the new JavaScript hooks and React hooks. With so much to learn, where do you start? I just learn what I need to get through the day and over time I feel like I figured this out pretty good. Join my mailing list and get access to a bunch of fun and informative resources that will help you learn WordPress hooks in PHP and JavaScript and the other stuff that’s important to know in order to write good code.

Featured image by me, via WordPress photos.

New eBook

Refactoring WordPress Plugins

The PHP Parts

 For experienced WordPress developers who are looking to improve the PHP in their plugins, use new modern best practices and adopt test-driven development.