State management in Gutenberg
State management in Gutenberg is centered around Redux, the JavaScript library used to handle application state. Gutenberg uses a structured approach to manage the various states within the editor interface, such as blocks, editor settings, and post data, through a system of stores, selectors, and actions.
State management
state management in Gutenberg allows developers to track, modify, and retrieve the state of the editor and its components using a structured approach based on Redux. Selectors fetch the state, actions update it, and middleware handles asynchronous operations, all of which are integrated into a real-time, responsive user experience.
Overview
The WordPress Data API is used to manage the global application state within the editor. It serves as an abstraction over the Redux library, a popular state management solution.
Understanding Redux API Design
In both Redux and the WordPress Data API, the concept of a Store is central. The store holds the global state, which can only be modified through actions. These actions are processed by reducers, which then define how the state evolves. To read the state, you can subscribe to the store and access the current state whenever it updates.
Simplifying the Redux API Design
Key Concepts and Terminology:
- Store: This is where the application’s state and logic reside. The store holds the state and uses reducers to manage state updates.
- dispatch: Used to trigger a change in the store’s state.
- select: Retrieves data from the store.
- subscribe: If you want to monitor changes to a store’s value, you need to subscribe to it. In React, this is done using hooks like
useSelect
anduseDispatch
.
WordPress Data Stores
The WordPress editor utilizes multiple stores rather than one global store, each designed for different parts of the site. Stores can be accessed using their namespace or imported directly from their respective packages.
core | WordPress Core Data | Interact with global settings and core data. |
core/blocks | Block Types Data | Manage registered blocks and styles. |
core/block-editor | Block Editor Data | Control the block editor, such as inserting or removing blocks. |
core/editor | Post Editor Data | Retrieve information about the current post, such as post type or attributes. |
core/edit-post | Editor UI Data | Manage the editor’s interface, toggling various UI components. |
core/notices | Notices Data | Manage notices within the editor. |
Working with Stores
You can interact with a store either by dispatching an action (to update the state) or by selecting data from it. To do so, you need to know the store’s namespace, or you can import it from the @wordpress/
package.
import { store } from '@wordpress/...';
import { store as packageStore } from '@wordpress/package';
When importing multiple stores, a common convention is to rename the store with a relevant name.
import { useDispatch } from '@wordpress/data';
import { store as packageStore } from '@wordpress/package';
const { doSomething } = useDispatch(packageStore);
Dispatching actions to update the store can be done through the useDispatch
hook:
import { useSelect } from '@wordpress/data';
import { store as packageStore } from '@wordpress/package';
const something = useSelect((select) => select(packageStore).getSomething());
To select data from a store, use the useSelect
hook:
import { useSelect } from '@wordpress/data';
import { store as packageStore } from '@wordpress/package';
const { something, somethingElse } = useSelect((select) => {
const { getSomething, getSomethingElse } = select(packageStore);
return {
something: getSomething(),
somethingElse: getSomethingElse()
};
});
For cases where you need multiple values from the same store, group them within a single useSelect
call:
Handling Loading and Error States
You can determine whether a select
call has been completed using the hasFinishedResolution
selector, available in all core stores. This method requires the selector name as its first parameter and any parameters passed to the selector as the second.
Example:
import { useSelect } from '@wordpress/data';
import { store as packageStore } from '@wordpress/package';
const something = useSelect((select) => {
const { getSomething, hasFinishedResolution } = select(packageStore);
const params = ['exampleParam'];
return {
something: getSomething(...params),
hasResolvedSomething: hasFinishedResolution('getSomething', params)
};
});
Data API vs. REST API
While the Data API leverages the REST API behind the scenes, it behaves differently. Data manipulated through the Data API is only saved when the post is explicitly saved. In contrast, direct interactions with the REST API modify and persist data immediately. This makes the Data API the better option for handling post-related data within the editor environment.
Examples of Using the Data API
Note: The useSelect
and useDispatch
hooks should only be used inside React components. Outside of React components, the select
, dispatch
, and subscribe
functions from @wordpress/data
can be used instead.
1. Retrieving the current post type and selected post template:
import { useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
const { postType, template } = useSelect((select) => ({
postType: select(editorStore).getCurrentPostType(),
template: select(editorStore).getEditedPostAttribute('template'),
}));
2. Inserting a block if it doesn’t already exist:
import { useSelect, useDispatch } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { useEffect } from '@wordpress/element';
import { createBlock } from '@wordpress/blocks';
const { insertBlocks } = useDispatch(blockEditorStore);
const blocks = useSelect((select) => select(blockEditorStore).getBlocks());
useEffect(() => {
const blockExists = blocks.some((block) => block.name === BLOCK_NAME);
if (!blockExists) {
insertBlocks(createBlock(BLOCK_NAME));
}
}, [blocks]);
3. Reading and updating post meta values:
import { useSelect, useDispatch } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
const { editPost } = useDispatch(editorStore);
const meta = useSelect((select) => select(editorStore).getEditedPostAttribute('meta'));
function setMetaValue(key, value) {
editPost({
meta: { [key]: value }
});
}
This guide provides a clear understanding of how to interact with the WordPress Data API, including accessing and updating state, handling multiple stores, and managing loading states.