Back to Blog
Lesson 16 of the Intermediate WordPress Plugins: REST API & React Admin course
WordPressJune 25, 20263 min read

Implementing CRUD in the Admin UI: WordPress REST API & React

Master CRUD operations in your WordPress admin dashboard. Learn to trigger API requests from React forms and lists to build truly interactive plugins.

WordPressReactCRUDAPI integrationREST APIadmin UIphpplugin-development

Previously in this course, we covered Building the Knowledge Base Service Layer | WordPress REST API to centralize our fetch logic. In this lesson, we move from passive data fetching to active data manipulation, connecting our React components to that service layer to perform full CRUD operations.

The Lifecycle of an Admin Action

In a React-based WordPress admin, "CRUD" isn't just about sending a request; it's about the feedback loop. When a user submits a form or deletes an item, the application must:

  1. Initiate: Trigger the API request via our service layer.
  2. Wait: Display a loading state or visual feedback.
  3. Sync: Update the local component state to reflect the server's truth.
  4. Notify: Provide success or error feedback to the user.

If you skip step 3, your UI becomes "stale," leading to user confusion when items they just deleted still appear on the screen.

Triggering POST and DELETE Requests

We’ll use our KnowledgeBaseService to handle the heavy lifting. In your list component, you'll want to map over your data array and attach a handler to a Button component from Working with @wordpress/components for WordPress Admin UIs.

Worked Example: Deleting an Item

Here is how you handle a deletion operation, ensuring the UI refreshes once the server confirms the action.

JAVASCRIPT
import { useState } from CE9178">'react';
import { Button } from CE9178">'@wordpress/components';
import { deleteArticle } from CE9178">'./services/api'; // From our service layer

const KnowledgeBaseList = ({ items, setItems }) => {
    const [isDeleting, setIsDeleting] = useState(false);

    const handleDelete = async (id) => {
        setIsDeleting(true);
        try {
            await deleteArticle(id);
            // Refresh UI by filtering out the deleted item
            setItems(items.filter(item => item.id !== id));
        } catch (error) {
            console.error("Deletion failed:", error);
            alert("Failed to delete article.");
        } finally {
            setIsDeleting(false);
        }
    };

    return (
        <ul>
            {items.map(item => (
                <li key={item.id}>
                    {item.title}
                    <Button 
                        isDestructive 
                        onClick={() => handleDelete(item.id)}
                        disabled={isDeleting}
                    >
                        Delete
                    </Button>
                </li>
            ))}
        </ul>
    );
};

Refreshing UI State

The most common mistake is failing to link the API response to the component state. In the example above, setItems(items.filter(...)) is the critical step. Because we are managing state locally, we must manually reconcile the UI with the database change.

For Create operations (POST), the flow is similar:

  1. Submit the form via createArticle(data).
  2. Receive the newly created object from the API response.
  3. Update the state: setItems([...items, newArticle]).

Hands-on Exercise

  1. Open your KnowledgeBaseList component.
  2. Implement a handleDelete function that calls your deleteArticle service method.
  3. Ensure your Button component is disabled while the request is in flight to prevent multiple accidental clicks.
  4. Update your items state using the functional update pattern to ensure you are working with the latest data.

Common Pitfalls

  • Race Conditions: If a user clicks "Delete" multiple times before the first request finishes, you may trigger errors. Always disable your buttons during the isDeleting or isSubmitting state.
  • Ignoring Nonces: Never forget that these requests require a nonce for security. Ensure your api-fetch setup is correctly configured as discussed in Localizing Data for JavaScript in WordPress Plugins.
  • Silent Failures: Always wrap your service calls in try/catch blocks. If the server returns a 403 (Permission Denied) or 500 (Internal Server Error), the user needs to know, and the application shouldn't just "hang."

Recap

We’ve successfully connected our React frontend to the backend API, allowing for real-time CRUD operations. By managing local state alongside our service layer calls, we ensure that our plugin remains performant and predictable. Remember: the API is the source of truth, but the React state is the source of the user's experience.

Up next: We will begin transitioning away from local component state to a more robust global architecture in Understanding WordPress Data Store Architecture.

Similar Posts