React Form with JSON Schema Form
A lightweight JSON schema form renderer lets you define form structure as data and render it dynamically — changing which fields appear means editing the schema object, not the component. This approach works well for admin tools, user-configurable forms, and APIs that return field definitions. The renderer shown here supports text, email, textarea, and select fields and weighs nothing.
The challenge
JSON schema forms often rely on heavy runtime engines and abstracted UI logic that is hard to debug and customize.
- Rendering different input types from a schema definition without a massive switch statement
- Managing state dynamically when the field list itself is data-driven
- Keeping the schema definition readable while supporting enough field types to be useful
- Handling select field options within the schema definition cleanly
Working code example
Here is a complete, self-contained working example you can drop directly into any React project. It uses Tailwind CSS for styling and requires no external dependencies beyond React itself.
import { useState } from 'react';
const schema = {
title: 'Support Ticket',
fields: [
{ name: 'name', label: 'Your Name', type: 'text', required: true },
{ name: 'email', label: 'Email Address', type: 'email', required: true },
{
name: 'category', label: 'Issue Category', type: 'select', required: true,
options: ['Bug Report', 'Feature Request', 'Billing', 'Account', 'Other'],
},
{ name: 'description', label: 'Description', type: 'textarea', required: true },
{ name: 'version', label: 'App Version (optional)', type: 'text', required: false },
],
};
function FormField({ field, value, onChange }) {
const cls = 'w-full border border-gray-300 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-teal-500';
if (field.type === 'textarea')
return <textarea value={value} onChange={onChange} rows={4} style={{ resize: 'none' }} className={cls} />;
if (field.type === 'select')
return (
<select value={value} onChange={onChange} className={`${cls} bg-white`}>
<option value="">Select…</option>
{field.options.map((o) => <option key={o} value={o}>{o}</option>)}
</select>
);
return <input type={field.type} value={value} onChange={onChange} className={cls} />;
}
export default function JsonSchemaForm() {
const init = Object.fromEntries(schema.fields.map((f) => [f.name, '']));
const [values, setValues] = useState(init);
const [errors, setErrors] = useState({});
const [submitted, setSubmitted] = useState(false);
const handleChange = (name, value) => {
setValues((v) => ({ ...v, [name]: value }));
if (errors[name]) setErrors((e) => ({ ...e, [name]: '' }));
};
const handleSubmit = (e) => {
e.preventDefault();
const errs = {};
schema.fields.forEach((f) => {
if (f.required && !values[f.name].trim()) errs[f.name] = `${f.label} is required`;
});
if (Object.keys(errs).length) { setErrors(errs); return; }
setSubmitted(true);
};
if (submitted)
return <p className="max-w-md mx-auto p-6 text-green-600 font-medium">Ticket submitted!</p>;
return (
<form onSubmit={handleSubmit} className="max-w-md mx-auto p-6 bg-white rounded-2xl shadow space-y-4">
<h2 className="text-xl font-bold text-gray-800">{schema.title}</h2>
{schema.fields.map((field) => (
<div key={field.name}>
<label className="block text-sm font-medium text-gray-700 mb-1">
{field.label}{field.required && <span className="text-red-400 ml-0.5">*</span>}
</label>
<FormField field={field} value={values[field.name]}
onChange={(e) => handleChange(field.name, e.target.value)} />
{errors[field.name] && <p className="text-red-500 text-xs mt-1">{errors[field.name]}</p>}
</div>
))}
<button type="submit"
className="w-full bg-teal-600 text-white font-semibold py-2 rounded-lg hover:bg-teal-700 transition">
Submit Ticket
</button>
</form>
);
}How ReactForm.co helps
ReactForm's visual builder handles all of the above — json schema form configuration, validation rules, state management, and responsive layout — without writing a single line of code. Drag fields onto the canvas, configure their properties in the sidebar, and get production-ready React output. You can publish the form and collect responses instantly, or export the JSX to drop into your own codebase.
Build this form visuallyFrequently asked questions
What is a JSON schema form?
A JSON schema form is one where the field definitions are stored as data (a JavaScript object or JSON) instead of hardcoded in JSX. A renderer component reads the schema and generates the form dynamically. This makes the form easily configurable — changing which fields appear requires editing the schema, not the component.
When should I use a schema-driven form vs a hardcoded form?
Use a schema-driven form when the field list comes from an API, when non-developers need to configure the form, or when you have many similar forms that share the same renderer. Use a hardcoded form when the fields are fixed and known at build time — the added abstraction of a schema renderer is unnecessary complexity for simple cases.
How do I initialize state dynamically from a schema?
Use Object.fromEntries with a map over the schema fields: const init = Object.fromEntries(schema.fields.map(f => [f.name, ""])). Pass this to useState. This generates the initial state object automatically, so adding or removing fields in the schema automatically updates the initial state without any manual changes.
Related topics
Build this form visually — no code needed
ReactForm.co handles json schema form fields, validation, conditional logic, and responsive layout automatically. Publish in minutes and collect responses for free.

