How to use the WordPress data package to manage application states

Managing the state of any WordPress app — how it handles and organizes data — can be challenging. As your project grows, keeping track of the flow of data and ensuring consistent updates across components becomes increasingly difficult. The WordPress data package can help here, as it provides a robust solution for state management.

This article looks into the WordPress data package, exploring its key concepts, implementation strategies, and best practices.

Introducing the WordPress data package

The WordPress data package — officially @wordpress/data — is a JavaScript (ES2015 and higher) state management library that provides a predictable and centralized way to manage application state. The right implementation can help make it easier to build complex user interfaces and handle data flow across your application.

The WordPress data package takes its inspiration from Redux, a popular state management library in the React ecosystem.

The Redux homepage, featuring a purple header section containing the Redux logo and title, along with a tagline and a Get Started button. Below this are four key feature sections with icons. Each section includes descriptive text explaining Redux's capabilities for application state management, debugging tools, and ecosystem compatibility. The top navigation bar contains links to various other pages on the site and search functionality.
The official Redux website.

Here, the data module works within the WordPress environment and provides integrations with WordPress-specific functionality and APIs. If you build for the WordPress Block Editor — or it’s something you have to support — the package will be crucial in managing its state. By using the same tools and patterns in your own plugins and themes, you can create a more consistent and familiar development experience.

The package’s relationship to Redux

While the WordPress data package draws inspiration from Redux, it’s not a direct port. There are many adaptations to fit the WordPress ecosystem, with some key differences between the two solutions:

  • The data package is designed to work seamlessly with WordPress APIs and functionality, which vanilla Redux can’t do without that adaptation.
  • Compared to Redux, the data package provides a more streamlined API. This can make it easier to get started.
  • Unlike Redux, the data package includes built-in support for asynchronous actions. If you work with the WordPress REST API, this will be useful.

The WordPress data package also has some comparisons with the REST API. While they both deal with data management, they serve different purposes:

In many cases, you’ll use both together: the REST API to fetch and update data on the server and the WordPress data package to manage that data within your application.

Key concepts and terminology for the WordPress data package

The WordPress data package offers an intuitive way of managing state. This refers to the data within a store. It represents the current condition of your application and can include both UI state (such as whether there’s an open modal) and data state (such as a list of posts).

The WordPress dashboard Posts page displaying a list of 106 posts with various filtering options at the top. This interface shows columns for title, author, categories, tags, and date. The left sidebar contains typical WordPress admin navigation items to other screens. Both published and scheduled content is included in the post list.
The state of your post data is one area the WordPress data package manages.

In this context, a store is the central hub of the WordPress data package. It holds the site’s entire state and provides the methods to access and update that state. In WordPress, you can have multiple stores. Each one will be responsible for a specific area of your site.

To manage those stores, you need a registry. This central object provides methods to register new stores and access your existing ones. A registry will hold stores, and those stores will hold your application state.

There are a few ways to work with state:

  • Actions describe the changes to a state. These are plain JavaScript objects and are the only way to trigger state updates. Actions will typically have a type property, which describes the action. It may also include additional data.
  • Selectors extract specific pieces of state from the store. These functions let you access state data without the need for direct interaction with the store’s structure. Resolvers are related and handle asynchronous data fetching. You use these to ensure that you can access the required data in a store before you run a selector.
  • Reducers specify how the state should change in response to actions. They take the current state and an action as arguments and return a new state object. Control functions let the reducers handle complex async operations without side effects.

You need to understand these fundamental concepts, as they all work together to create a robust state management system with stores at its heart.

Stores: the central hub of the WordPress data package

Stores are the containers for your application’s state and provide the methods to interact with it. The WordPress data package bundles together a few other packages, and each of these registers stores for the Block directory, Block Editor, core, post editing, and more.

Each store will have a unique namespace, such as core, core/editor, and core/notices. Third-party plugins will register stores, too, so you need to choose unique namespaces to avoid conflicts. Regardless, stores you register will live in the default registry in most cases.

This central object has a few responsibilities:

  • Registering new stores.
  • Providing access to existing stores.
  • Managing subscriptions to state changes.

While you don’t often have direct interaction with the registry, you do need to understand its role in how the data package orchestrates state management across WordPress.

Basic interaction with WordPress data stores

If you use ES2015+ JavaScript and you are working with a WordPress plugin or theme, you can include it as a dependency:

npm install @wordpress/data --save

Within your code, you’ll import the necessary functions from the package at the top of the file:

import { select, dispatch, subscribe } from '@wordpress/data';

Interacting with existing WordPress stores requires that you use some of the functions you import. Accessing state data with select, for instance:

const posts = select('core').getPosts();

It’s the same for dispatching actions:

dispatch('core').savePost(postData);

Subscribing to state changes uses a slightly different format, but the concept is the same:

subscribe(() => {
  const newPosts = select('core').getPosts();
  // Update your UI based on the new posts
});

However, you won’t always work with the default stores. Often, you will work with existing additional stores or register your own.

How to register a WordPress data store

Defining your store’s configuration and registering it with the WordPress data package starts with importing the register function:

…
import { createReduxStore, register } from '@wordpress/data';
…

This takes a single argument — your store descriptor. Next, you should define a default state for the store to set its default values:

…
const DEFAULT_STATE = {
  todos: [],
};
…

Next, create an actions object, define a reducer function to handle state updates, and create a selectors object with functions to access state data:

const actions = {
  addTodo: (text) => ({
    type: 'ADD_TODO',
    text,
  }),
};

const reducer = (state = DEFAULT_STATE, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
      ...state,
      todos: [...state.todos, { text: action.text, completed: false }],
      };
    default:
      return state;
   }
};

const selectors = {
  getTodos: (state) => state.todos,
};

To create the store configuration, define it using the createReduxStore object. This will initialize the actions, selectors, control, and other properties for your store:

const store = createReduxStore('my-plugin/todos', {
  reducer,
  actions,
  selectors,
});

The minimum this object needs is a reducer to define the shape of the state and how it changes in response to other actions. Finally, register the store, calling the store descriptor you define with createReduxStore:

register(store);

You can now interact with your custom store as you would others:

import { select, dispatch } from '@wordpress/data';
// Add a new todo
dispatch('my-plugin/todos').addTodo('Learn WordPress data package');
// Get all todos
const todos = select('my-plugin/todos').getTodos();

The key in using the WordPress data package is how you use the different properties and objects at your disposal.

Breaking down the five WordPress data store properties

Much of using the WordPress data package happens “backwards” — defining low-level data store properties before the store itself. The createReduxStore object is a perfect example, as it pulls together all of the definitions you make to create the descriptor you use to register a store:

import { createReduxStore } from '@wordpress/data';
  const store = createReduxStore( 'demo', {
    reducer: ( state = 'OK' ) => state,
    selectors: {
    getValue: ( state ) => state,
    },
  } );

These other properties also need setup and configuration.

1. Actions

Actions are the primary way to trigger state changes in your store. They are plain JavaScript objects that describe what should happen. As such, it can be a good idea to create these first, as you can decide what states you want to retrieve.

const actions = {
  addTodo: (text) => ({
    type: 'ADD_TODO',
    text,
  }),
  toggleTodo: (index) => ({
    type: 'TOGGLE_TODO',
    index,
  }),
};

Action creators take optional arguments and will return an object to pass along to the reducer you define:

const actions = {
  updateStockPrice: (symbol, newPrice) => {
  return {
    type: 'UPDATE_STOCK_PRICE',
    symbol,
    newPrice
  };
},

If you pass in a store descriptor, you can dispatch action creators and update the state value:

dispatch('my-plugin/todos').updateStockPrice('¥', '150.37');

Consider action objects as instructions for the reducer on how to make state changes. At a minimum, you will likely want to define create, update, read, and delete (CRUD) actions. It could also be that you have a separate JavaScript file for action types and create an object for all of those types, especially if you define them as constants.

2. Reducer

It’s worth talking about the reducer here, because of its central role alongside actions. Its job is to specify how the state should change in response to the instructions it gets from an action. If you pass it the instructions from the action and the current state, it can return a new state object and pass it along the chain:

const reducer = (state = DEFAULT_STATE, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, { text: action.text, completed: false }],
      };
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map((todo, index) =>
          index === action.index ? { ...todo, completed: !todo.completed } : todo
        ),
    };
    default:
      return state;
    }
};

Note that a reducer must be a pure function, and it shouldn’t mutate the state it accepts (rather, it should return it with updates). Reducers and actions have a symbiotic relationship in many respects, so grasping how they work together is important.

3. Selectors

In order to access the current state from a registered store, you need selectors. It’s the primary way you “expose” your store state, and they help keep your components decoupled from the store’s internal structure:

const selectors = {
  getTodos: (state) => state.todos,
  getTodoCount: (state) => state.todos.length,
};

You can call those selectors with the select function:

const todoCount = select('my-plugin/todos').getTodoCount();

However, a selector doesn’t send that data anywhere: it simply reveals it and provides access.

Selectors can receive as many arguments as you need to access the state with accuracy. The value it returns is the result of whatever those arguments achieve within the selector you define. As with actions, you might choose to have a separate file to hold all of your selectors, as there could be a lot of them.

4. Controls

Guiding the execution flow of your site’s functionality — or executing logic within it — is where you use controls. These define the behavior of execution flows for your actions. Consider these the assistants in the WordPress data package as they work as go-betweens to gather the state to pass to resolvers.

Controls also handle side effects in your store, such as API calls or interactions with browser APIs. They let you keep reducers clean while still enabling you to handle complex async operations:

const controls = {
  FETCH_TODOS: async () => {
    const response = await fetch('/api/todos');
    return response.json();
  },
};

const actions = {
  fetchTodos: () => ({ type: 'FETCH_TODOS' }),
};

This cycle of fetching and returning data is crucial to the entire process. But without a call from an action, you won’t be able to use that data.

5. Resolvers

Selectors expose a store’s state, but don’t explicitly send that data anywhere. Resolvers meet selectors (and controls) to retrieve the data. Like controls, they can also handle asynchronous data fetching.

const resolvers = {
  getTodos: async () => {
    const todos = await controls.FETCH_TODOS();
    return actions.receiveTodos(todos);
  },
};

The resolver ensures that the data you ask for is available in the store before running a selector. This tight connectivity between the resolver and the selector means they need to match names. This is so the WordPress data package can understand which resolver to invoke based on the data you request.

What’s more, the resolver will always receive the same arguments that you pass into a selector function, and will also either return, yield, or dispatch action objects.

Error handling when using the WordPress data package

You must implement proper error handling when working with the WordPress data package. If you choose to deal with asynchronous operations, work with full stack deployments, or make API calls, it’s even more vital.

For example, if you dispatch actions that involve asynchronous operations, a try-catch block could be a good option:

const StockUpdater = () => {
  // Get the dispatch function
  const { updateStock, setError, clearError } = useDispatch('my-app/stocks');
  const handleUpdateStock = async (stockId, newData) => {
    try {
      // Clear any existing errors
      clearError();
      // Attempt to update the stock
      await updateStock(stockId, newData);
    } catch (error) {
      // Dispatch an error action if something goes wrong
      setError(error.message);
    }
};

  return (
    <button onClick={() => handleUpdateStock('AAPL', { price: 150 })}>
      Update Stock
    </button>
  );
};

For reducers, you can handle error actions and update the state:

const reducer = (state = DEFAULT_STATE, action) => {
  switch (action.type) {
    // ... other cases
    case 'FETCH_TODOS_ERROR':
      return {
      ...state,
      error: action.error,
      isLoading: false,
    };
    default:
      return state;
  }
};

When using selectors, you can include error checking to handle potential issues then check for errors in your components before you use the data.:

const MyComponent = () => {
  // Get multiple pieces of state including error information
  const { data, isLoading, error } = useSelect((select) => ({
    data: select('my-app/stocks').getStockData(),
    isLoading: select('my-app/stocks').isLoading(),
    error: select('my-app/stocks').getError()
  }));

  // Handle different states
  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return (
      <div className="error-message">
        <p>Error loading stocks: {error.message}</p>
        <button onClick={retry}>Try Again</button>
      </div>
    );
  }
  return (
    <div>
      {/* Your normal component render */}
    </div>
  );
};

The useSelect and useDispatch functions give you a lot of power to handle errors within the WordPress data package. With both of these, you can pass in custom error messages as arguments.

It’s good practice to ensure you centralize your error state during initial configuration, and to keep error boundaries at the component level. Employing error handling for loading states will also help to keep your code clear and consistent.

How to integrate your WordPress data store with your site

There’s a lot that the WordPress data package can do to help you manage state. Putting all of this together is also a practical consideration. Let’s look at a stock ticker that displays and updates financial data in real-time.

The first task is to create a store for your data:

import { createReduxStore, register } from '@wordpress/data';

const DEFAULT_STATE = {
  stocks: [],
  isLoading: false,
  error: null,
};

const actions = {
  fetchStocks: () => async ({ dispatch }) => {
  dispatch({ type: 'FETCH_STOCKS_START' });
  try {
    const response = await fetch('/api/stocks');
    const stocks = await response.json();
    dispatch({ type: 'RECEIVE_STOCKS', stocks });
  } catch (error) {
    dispatch({ type: 'FETCH_STOCKS_ERROR', error: error.message });
    }
  },
};

const reducer = (state = DEFAULT_STATE, action) => {
  switch (action.type) {
    case 'FETCH_STOCKS_START':
      return { ...state, isLoading: true, error: null };
    case 'RECEIVE_STOCKS':
      return { ...state, stocks: action.stocks, isLoading: false };
    case 'FETCH_STOCKS_ERROR':
      return { ...state, error: action.error, isLoading: false };
    default:
      return state;
  }
};

const selectors = {
  getStocks: (state) => state.stocks,
  getStocksError: (state) => state.error,
  isStocksLoading: (state) => state.isLoading,
};

const store = createReduxStore('my-investing-app/stocks', {
  reducer,
  actions,
  selectors,
});

register(store);

This process sets up a default state that includes error and loading states, along with your actions, reducers, and selectors. Once you define these, you can register the store.

Display the store’s data

With a store in place, you can create a React component to display the information within it:

import { useSelect, useDispatch } from '@wordpress/data';
import { useEffect } from '@wordpress/element';

const StockTicker = () => {
  const stocks = useSelect((select) => select('my-investing-app/stocks').getStocks());
  const error = useSelect((select) => select('my-investing-app/stocks').getStocksError());
  const isLoading = useSelect((select) => select('my-investing-app/stocks').isStocksLoading());

  const { fetchStocks } = useDispatch('my-investing-app/stocks');

  useEffect(() => {
    fetchStocks();
  }, []);

  if (isLoading) {
    return <p>Loading stock data...</p>;
  }

  if (error) {
    return <p>Error: {error}</p>;
  }

  return (
    <div className="stock-ticker">
      <h2>Stock Ticker</h2>
      <ul>
       {stocks.map((stock) => (
       <li key={stock.symbol}>
        {stock.symbol}: ${stock.price}
       </li>
       ))}
     </ul>
   </div>
  );
};

This component brings in the useSelect and useDispatch hooks (along with others) to handle data access, dispatching actions, and component lifecycle management. It also sets custom error and loading state messages, along with some code to actually display the ticker. With this in place, you now have to register the component with WordPress.

Registering the component with WordPress

Without registration within WordPress, you won’t be able to use the components you create. This means registering it as a Block, although it could be a widget if you design for Classic Themes. This example uses a Block.

import { registerBlockType } from '@wordpress/blocks';
import { StockTicker } from './components/StockTicker';

registerBlockType('my-investing-app/stock-ticker', {
  title: 'Stock Ticker',
  icon: 'chart-line',
  category: 'widgets',
  edit: StockTicker,
  save: () => null, // This will render dynamically
});

This process will follow the typical approach you’ll take to register Blocks within WordPress, and doesn’t require any special implementation or setup.

Managing state updates and user interactions

Once you register the Block, you have to handle user interactions and real-time updates. This will need some interactive controls, along with custom HTML and JavaScript:

const StockControls = () => {
  const { addToWatchlist, removeFromWatchlist } = useDispatch('my-investing-app/stocks');
  return (
    <div className="stock-controls">
      <button onClick={() => addToWatchlist('AAPL')}>
        Add Apple to Watchlist
      </button>

      <button onClick={() => removeFromWatchlist('AAPL')}>
        Remove from Watchlist
      </button>
    </div>
  );
};

For real-time updates, you can set up an interval within the React component:

useEffect(() => {
  const { updateStockPrice } = dispatch('my-investing-app/stocks');
  const interval = setInterval(() => {
    stocks.forEach(stock => {
      fetchStockPrice(stock.symbol)
        .then(price => updateStockPrice(stock.symbol, price));
    });
  }, 60000);

  return () => clearInterval(interval);
}, [stocks]);

This approach keeps your component’s data in sync with your store while maintaining a clear separation of concerns. The WordPress data package will handle all state updates, which gives your app consistency.

Server-side rendering

Finally, you can set up server-side rendering to ensure that the stock data is current upon page load. This requires some PHP knowledge:

function my_investing_app_render_stock_ticker($attributes, $content) {
  // Fetch the latest stock data from your API
  $stocks = fetch_latest_stock_data();
  ob_start();
  ?>
  <div class="stock-ticker">
    <h2>Stock Ticker</h2>
    <ul>
      <?php foreach ($stocks as $stock) : ?>
        <li><?php echo esc_html($stock['symbol']); ?>: $<?php echo esc_html($stock['price']); ?></li>
      <?php endforeach; ?>
    </ul>
  </div>

  <?php
  return ob_get_clean();
}

register_block_type('my-investing-app/stock-ticker', array(
  'render_callback' => 'my_investing_app_render_stock_ticker'
));

This approach provides a complete integration of your data store with WordPress, handling everything from initial render to real-time updates and user interactions.

Summary

The WordPress data package is a complex yet robust way to manage application states for your projects. Beyond the key concepts lies a rabbit hole of functions, operators, arguments, and more. Remember, though, that not every piece of data needs to be in a global store — the local component state still has a place in your code.

Do you see yourself using the WordPress data package on a regular basis, or do you have another method of managing state? Share your opinions with us in the comments below.

The post How to use the WordPress data package to manage application states appeared first on Kinsta®.

版权声明:
作者:siwei
链接:https://www.techfm.club/p/178856.html
来源:TechFM
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>