Creating a React Form for Submissions in WordPress
Learn to build interactive React forms in WordPress using controlled components. Master state management and submission events for your admin dashboard.
Previously in this course, we covered working with @wordpress/components to build the visual shell of our admin dashboard. Now that we have the layout, it's time to make it functional. In this lesson, we are building the "Add Entry" form, which is the heart of our plugin's data entry interface.
To build effective user interfaces, we must master the concept of Forms and Controlled components. In React, a controlled component is a form element whose value is driven entirely by state, making the data flow predictable and easy to debug.
Understanding Controlled Components
In vanilla HTML, form elements like <input> or <textarea> maintain their own internal state (the text currently typed into the box). In React, we shift that responsibility to the component's state.
When you use a controlled component, the input's value attribute is tied to a variable in your useState hook. Every time the user types, an onChange event fires, and we update that state variable. React then re-renders the component with the new value. This cycle—State → UI → Event → State—is the foundation of modern React development.
Building the Knowledge Base Entry Form
Let’s implement a form to capture a title and description for our Knowledge Base. We will use the useState hook to track the input values and a handler function to manage form submission.
JSXimport { useState } from CE9178">'@wordpress/element'; import { Button, TextControl, TextareaControl } from CE9178">'@wordpress/components'; const KnowledgeBaseForm = () => { // Initialize state for form fields const [title, setTitle] = useState(CE9178">''); const [content, setContent] = useState(CE9178">''); const handleSubmit = (event) => { // Prevent the browser from performing a standard page reload event.preventDefault(); console.log(CE9178">'Submitting:', { title, content }); // Reset the form after submission setTitle(CE9178">''); setContent(CE9178">''); }; return ( <form onSubmit={handleSubmit}> <TextControl label="Entry Title" value={title} onChange={(value) => setTitle(value)} /> <TextareaControl label="Entry Content" value={content} onChange={(value) => setContent(value)} /> <Button isPrimary type="submit"> Add Entry </Button> </form> ); }; export default KnowledgeBaseForm;
Breaking Down the Implementation
- State Initialization: We use
useState('')to initialize our fields as empty strings. - Binding: The
valueprop of theTextControlis explicitly set to our state variable (title). If you remove theonChangehandler, the input would become read-only because the state never updates to reflect the user's typing. - Event Handling: The
handleSubmitfunction includesevent.preventDefault(). This is critical; without it, the browser will attempt a traditional GET/POST request, triggering a page refresh that destroys your React application's state.
Hands-on Exercise: Adding a Category Field
Now it's your turn to extend the form.
- Add a new
TextControlto the form above specifically for a "Category" field. - Add a new state variable called
categoryusinguseState. - Update the
handleSubmitfunction to include thecategoryin theconsole.logoutput. - Verify that the input clears after you click the "Add Entry" button.
Common Pitfalls
When building Forms with Controlled components, developers often trip over these three issues:
- Forgetting
event.preventDefault(): As mentioned, this is the #1 cause of "why does my page reload when I submit?". Always include it as the first line in your submit handler. - Missing
onChangehandlers: If you define avalueprop on an input but omit theonChangehandler, the input will appear "stuck" and the user won't be able to type anything. - Over-complicating State: For simple forms, individual
useStatecalls are fine. However, if your form grows to 10+ fields, consider using a single state object withuseState({ title: '', content: '', category: '' })and a single change handler that updates the object using the spread operator:setFormData({ ...formData, [name]: value }).
By mastering these patterns, you ensure that your plugin's data layer remains consistent with the UI. In the next lesson, we will connect this form to our REST API to persist data to the database.
Up next: Implementing CRUD in the Admin UI
Work with me

Custom WordPress Plugin Development
Custom WordPress & WooCommerce plugins built to standard — by the developer behind a plugin with 5,000+ active installs and a SaaS with 10,000+ users.

Headless WordPress + Next.js Frontend Development
Keep WordPress for content, get a lightning-fast Next.js frontend. The best of both worlds — familiar editing, modern speed.