React JSON Schema Forms in Practice — Why They Break Down and How SurveyJS Fixes the Architecture
Source: Dev.to
The Root Cause
React JSON Schema libraries treat the schema as both structure and behavior. They rely on schema mutation (dependencies, oneOf, dynamically regenerated schemas) and depend heavily on React re‑renders to apply logic. Most of the problems below stem directly from that conflation.
SurveyJS takes a different approach: JSON is treated as a declarative model, business logic is encapsulated in the form engine (survey‑core), and React is used only for rendering, not orchestration.
Conditional Fields in RJSF
In RJSF, conditional fields are typically implemented using dependencies, oneOf, or dynamically regenerated schemas:
const schema = {
type: "object",
properties: {
hasCar: { type: "boolean" }
},
dependencies: {
hasCar: {
oneOf: [
{
properties: {
hasCar: { const: true },
carModel: { type: "string" }
}
},
{
properties: {
hasCar: { const: false }
}
}
]
}
}
};
This works only as long as the schema never changes shape unexpectedly. In React JSON Schema forms, the schema is not just a description of fields—it is the form itself. Using dependencies, oneOf, or anyOf dynamically switches schema branches at runtime. From React’s perspective, fields are added or removed, the internal form tree changes shape, and previously mounted inputs may unmount and remount.
That is manageable when:
- The schema is static.
- Conditionals are simple.
- No external data influences the schema.
Real‑world product forms, however, often need:
- Feature flags.
- Backend‑driven configuration.
- Role‑based visibility.
- Progressive disclosure.
As soon as the schema is modified dynamically (e.g., regenerated in a useEffect), React sees it as a new form definition, not a continuation of the previous one. Input state, defaults, and validation can reset in surprising ways.
Render‑Cycle Coupling
React JSON Schema form libraries also rely on React’s render cycle to coordinate behavior:
- A field change updates
formData. - React re‑renders.
- A different schema branch may be selected and then validated.
This chain only works when updates happen in perfect order. React may batch updates or re‑render components opportunistically, which can lead to:
- Flickering fields.
- Disappearing validation messages.
- Inputs losing focus.
None of this is a React bug—it is a consequence of encoding business logic into the render cycle.
SurveyJS: Static Schema + Runtime Expressions
SurveyJS does not mutate schemas to express behavior. Instead, conditions are runtime expressions evaluated by survey‑core:
{
"name": "carModel",
"type": "text",
"visibleIf": "{hasCar} = true"
}
Key ideas
- The schema stays static—the form’s structure is defined once and treated as a durable contract.
- Fields are never removed or recreated; they always exist in the model, giving the engine a stable reference point.
- Visibility is evaluated at runtime by the engine, making field appearance deterministic and independent of React render timing.
React is never asked to rebuild the form. It simply renders what the engine reports as visible or active. State changes trigger rule evaluation, not structural mutation, eliminating the need for schema regeneration, deep‑equality checks, or defensive memoization. This separation allows SurveyJS to handle complex conditional logic, large schemas, and frequent state changes without becoming fragile.
Conditional logic in SurveyJS is therefore predictable, composable, and scalable across even very large forms.
Validation
In React JSON Schema forms, validation is typically delegated to Ajv and mapped back into React state:
// Example JSX (simplified)
<Form
schema={schema}
onChange={...}
validate={...}
/>
Teams often encounter issues such as:
- Disappearing errors after schema updates.
- Async validation racing with renders.
- Complex cross‑field validation requiring custom plumbing.
The underlying problem isn’t Ajv—it’s that validation is tied to React render cycles. Any schema or state change risks resetting the error tree.
SurveyJS Validation
SurveyJS treats validation as a first‑class engine concern:
{
"name": "email",
"type": "text",
"isRequired": true,
"validators": [
{ "type": "email" }
]
}
- Validation runs independently of React.
- Errors are persisted in the survey model.
- Cross‑field or async validation are built‑in.
Moving validation out of React removes the need to sequence setState calls, debounce validation manually, or reconcile async results with rendering. Complex rules stay readable because they are declared close to the fields they affect, rather than scattered across component logic.
Dynamic Schema Regeneration – A Common Pitfall
In RJSF, changing the schema usually means replacing the schema object, often in a useEffect:
const [schema, setSchema] = useState(baseSchema);
useEffect(() => {
if (formData.type === "advanced") {
setSchema(advancedSchema);
}
}, [formData]);
This pattern causes a full form reinitialization, loss of user input, and broken validation. SurveyJS avoids this by keeping the schema static and handling all conditional logic inside the engine.
Summary
| Aspect | React JSON Schema Forms (RJSF) | SurveyJS Form Library |
|---|---|---|
| Schema role | Structure and behavior (mutated) | Pure declarative model (static) |
| Conditional logic | dependencies, oneOf, schema regeneration | Runtime expressions (visibleIf, enableIf, etc.) |
| State handling | Tied to React render cycle; can cause unmount/remount | Engine maintains stable field instances |
| Validation | Ajv + React state; prone to reset on schema change | Engine‑driven, persisted in model |
| Scalability | Becomes brittle with large, dynamic forms | Predictable, composable, works for large schemas |
By treating the JSON as a static contract and moving business logic into a dedicated engine, SurveyJS separates what the form looks like from how it behaves. This results in more reliable, maintainable, and scalable forms—especially when dealing with complex, dynamic, or large‑scale applications.
Why SurveyJS Beats React JSON‑Schema Forms for Large, Dynamic Forms
The problem with React JSON‑Schema libraries
- Schema mutation – Updating the JSON schema (adding/removing fields, changing validation) forces a full re‑render.
- State loss – User input, validation status, and UI state are often reset when the schema changes.
- Performance hit – Large schemas cause slow initial renders, expensive deep‑object comparisons, and unnecessary re‑renders of unrelated fields.
Developers try to mitigate these issues with memoization, deep merges, or schema‑diffing, but those work‑arounds add complexity and still don’t solve the root cause.
SurveyJS’s approach
- Static schema – The JSON schema never changes at runtime.
- Runtime expressions – Conditional visibility, required‑field logic, and validation are expressed as formulas evaluated by the SurveyJS engine.
- Separation of concerns –
- survey‑core – Platform‑agnostic model that holds the form definition, state, and business logic.
- survey‑react‑ui – Thin React wrapper that simply renders whatever the engine reports as “active”.
Because React is only responsible for rendering, it never has to evaluate conditions, manage validation state, or reconcile schema changes. This yields:
- Predictable behavior
- Preservation of user input & validation state across dynamic changes
- Much better performance for large forms
Performance advantages
| Issue | React JSON‑Schema libs | SurveyJS |
|---|---|---|
| Initial render of huge schemas | Slow (full tree render) | Fast (incremental rendering) |
| Updating a single field | May trigger whole‑form re‑render | Only affected fields re‑evaluate |
| Deep comparison on each render | Expensive | Avoided – engine handles diffing |
| Lazy loading of pages/questions | Rarely supported | Built‑in, renders only visible items |
Minimal example (SurveyJS in React)
import { Survey } from "survey-react-ui";
import { Model } from "survey-core";
const surveyJson = {
/* … your survey definition … */
};
export default function SurveyComponent() {
const survey = new Model(surveyJson); // engine handles logic
return <Survey model={survey} />; // React only renders UI
}
Behind the scenes – The SurveyJS engine evaluates visibility conditions, dynamic rules, and validation in real time without diffing the entire schema.
Packages you’ll use
- survey‑core – Platform‑independent survey model (business logic).
- survey‑react‑ui – React‑specific renderer.
- survey‑creator‑core – Model for the drag‑and‑drop Survey Creator.
- survey‑creator‑react – React UI for the Survey Creator.
Learn more about the architecture: SurveyJS Architecture (link).
When to choose SurveyJS
- Forms are long‑lived and evolve over time.
- Conditional logic (show/hide, enable/disable, dynamic validation) is central.
- You need high performance at scale (hundreds of fields, multi‑page surveys).
- Validation is non‑trivial (custom expressions, cross‑field rules).
- Maintaining a mutable JSON schema becomes a maintenance burden.
React JSON‑Schema forms are still great for simple, static forms, but for the scenarios above SurveyJS provides a cleaner, faster, and more maintainable solution.
Resources
- Demo: Content‑Heavy JSON Forms – a live example of SurveyJS handling large, dynamic forms.
- Documentation:
Most issues blamed on “React JSON‑Schema form” are not bugs but inherent architectural limits. SurveyJS moves the heavy lifting out of React, letting product teams focus on features instead of fighting render cycles.