Strapi v5 and React Forms: A New Approach
Managing form data and structure can quickly become a bottleneck. When a non-developer needs to update form fields, the usual path involves frontend code changes. This is where Strapi v5, combined with React, offers a more flexible solution, particularly when leveraging plugins like FormFlow. This approach decouples form definition from its rendering, allowing administrators to modify form structures directly within Strapi while the user interface remains in React.
The core idea is to keep the form's schema and configuration within Strapi v5. This means that changes to fields, validation rules, or even the order of form elements can be made through Strapi's admin interface. React components then fetch this definition and render the form dynamically. Submission handling also flows through Strapi, ensuring data integrity, spam control, and a centralized place for reviewing submissions.
The data flow is intentionally kept simple and efficient:
Strapi admin builder
↓
GET /api/formflow/forms/:slug
↓
Your React components
↓
POST /api/formflow/forms/:slug/submit
↓
Strapi submission inbox
This architecture avoids the complexity and potential limitations of iframes or hosted widgets. The React SDK, in this setup, typically ships no CSS, giving developers full control over the form's styling within their React application.
Installing the Strapi Plugin
To implement this workflow, you first need to install the necessary Strapi plugin. Assuming you are using FormFlow, the installation process is straightforward within your Strapi v5 project. Navigate to your Strapi project directory in your terminal and run the following command:
npm install @strapi/plugin-formflow
# or
yarn add @strapi/plugin-formflow
After installation, you need to enable the plugin by adding it to your config/plugins.js file. If this file does not exist, create it. The configuration should look like this:
// config/plugins.js
module.exports = {
'formflow': {
enabled: true,
},
};
Restart your Strapi server for the plugin to become active. Once restarted, you should see a new "FormFlow" section in your Strapi admin panel, where you can begin defining your forms.
Defining Forms in Strapi
With the plugin installed and enabled, you can now create form definitions directly in the Strapi admin. Navigate to the "FormFlow" section and click "Create new form." You will be prompted to give your form a unique slug, which will be used in the API endpoints. For example, a contact form might have the slug contact-us.
The form builder interface allows you to add various field types, such as text inputs, text areas, email fields, select dropdowns, and checkboxes. For each field, you can define:
- Field Name: A unique identifier for the field (e.g.,
fullName,emailAddress). This will be used as the key in your submission data. - Label: The text displayed to the user for the form field.
- Type: The input type (e.g.,
text,email,textarea,select,checkbox). - Required: A boolean flag to indicate if the field must be filled.
- Validation Rules: Options for server-side validation, such as minimum/maximum length, regular expression patterns, or specific formats (like email).
- Placeholder Text: Hint text displayed within the input field.
- Options (for select/radio): If the field type is
selectorradio, you can define the available options.
The ability to configure these elements directly in Strapi means that marketing teams or content editors can make adjustments to form fields without needing to involve developers. This significantly speeds up iteration cycles for forms that require frequent updates.
Integrating with React
On the React side, you'll need to fetch the form definition from Strapi and then render the form dynamically. The FormFlow plugin typically provides an API endpoint to retrieve the form structure based on its slug.
Here’s a conceptual example of how you might fetch and render a form in a React component:
import React, { useState, useEffect } from 'react';
function DynamicForm({ slug }) {
const [formDefinition, setFormDefinition] = useState(null);
const [formData, setFormData] = useState({});
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchForm = async () => {
try {
const response = await fetch(`YOUR_STRAPI_URL/api/formflow/forms/${slug}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setFormDefinition(data.data);
// Initialize formData based on form definition
const initialData = {};
data.data.attributes.fields.forEach(field => {
initialData[field.name] = ''; // Or appropriate default
});
setFormData(initialData);
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
};
fetchForm();
}, [slug]);
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const response = await fetch(`YOUR_STRAPI_URL/api/formflow/forms/${slug}/submit`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ data: formData }),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
}
alert('Form submitted successfully!');
// Optionally reset form or redirect
setFormData({}); // Reset form
} catch (e) {
setError(e.message);
} finally {
setLoading(false);
}
};
if (loading) return <p>Loading form...</p>;
if (error) return <p>Error loading form: {error.message}</p>;
if (!formDefinition) return <p>No form definition found.</p>;
const { fields } = formDefinition.attributes;
return (
<form onSubmit={handleSubmit}>
{fields.map(field => (
<div key={field.name}>
<label htmlFor={field.name}>{field.label}
{field.required && '*'}
</label>
{field.type === 'textarea' ? (
<textarea
id={field.name}
name={field.name}
value={formData[field.name] || ''}
onChange={handleChange}
placeholder={field.placeholder}
required={field.required}
/>
) : field.type === 'select' ? (
<select
id={field.name}
name={field.name}
value={formData[field.name] || ''}
onChange={handleChange}
required={field.required}
>
{field.options && field.options.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
) : field.type === 'checkbox' ? (
<input
type="checkbox"
id={field.name}
name={field.name}
checked={!!formData[field.name]}
onChange={handleChange}
/>
) : (
<input
type={field.type}
id={field.name}
name={field.name}
value={formData[field.name] || ''}
onChange={handleChange}
placeholder={field.placeholder}
required={field.required}
/>
)}
</div>
))}
<button type="submit" disabled={loading}>
{loading ? 'Submitting...' : 'Submit'}
</button>
{error && <p style={{ color: 'red' }}>Submission Error: {error}</p>}
</form>
);
}
export default DynamicForm;
This React component fetches the form structure, initializes its state, and renders the appropriate input elements based on the `type` defined in Strapi. The handleChange function updates the form state as the user types, and handleSubmit sends the data to the Strapi submission endpoint.
Server-Side Validation and Submission Handling
A key advantage of this approach is robust server-side validation, managed by Strapi. When the React form is submitted, the data is sent to the /api/formflow/forms/:slug/submit endpoint. Strapi then validates the submitted data against the rules defined in the admin builder. This prevents invalid data from ever reaching your database.
If validation passes, the submission is stored in a dedicated "Submissions" section within the FormFlow plugin in your Strapi admin panel. This section acts as an inbox, allowing you to review all form entries. This centralized management is crucial for teams that need to process inquiries or data collected through forms without direct access to the frontend codebase.
Spam control mechanisms can also be integrated at the Strapi level, further securing your forms.
Benefits of this Decoupled Approach
This method offers several significant advantages:
- Flexibility: Form structure and content can be changed by non-technical users via the Strapi admin.
- Developer Control: Developers retain full control over the UI/UX and styling within their React application.
- Maintainability: Reduces frontend code churn for form modifications.
- Centralized Management: All form definitions and submissions are managed within Strapi.
- No External Dependencies: Eliminates reliance on third-party hosted widgets or iframes, improving performance and security.
By using Strapi v5 with a plugin like FormFlow, you can build highly dynamic and manageable forms in React that empower both developers and content administrators.
