Most React forms mix several responsibilities in the same place:
- data definition
- UI components
- validation logic
- submission workflows
At first this feels natural. A form component renders inputs, validates data, and submits the data to an API.
But as applications grow, this approach becomes harder to maintain. Forms start to:
- reuse the same validation rules
- trigger shared workflows
- appear dynamically from a backend
- evolve independently from UI implementations
When data structure, UI rendering, and business logic are tightly coupled inside React components, even small changes can ripple across the codebase.
Why Traditional React Forms Don't Scale
The main challenge is that forms often become mini applications inside a single React component.
Over time, the component starts handling multiple responsibilities:
- defining the data structure
- rendering UI inputs
- performing validation
- coordinating submission workflows
- integrating with APIs
This works for small forms, but the complexity grows quickly when applications require:
- dozens or hundreds of forms
- reusable validation logic
- domain-specific field types
- dynamic forms generated from APIs or databases
- visual form builders or low-code systems
When UI, data, and business logic are tightly coupled, forms become harder to reuse, extend, or generate dynamically.
This is the problem Formitiva tries to solve.
Formitiva introduces a registry system that separates form data, UI components, and business logic. Instead of embedding validation and submission logic inside form components, those behaviors are registered once and referenced by schema definitions.
Formitiva in One Sentence
Formitiva lets you define forms as schema data while registering the UI and logic those schemas should use.
A form definition can stay small and declarative:
const userInformationDefinition = {
name: 'user_information',
version: '1.0.0',
displayName: 'User Information',
validationHandlerName: 'user:passwordMatch',
submitHandlerName: 'user:userDataSubmission',
properties: [
{
name: 'userName',
displayName: 'User Name',
type: 'text',
defaultValue: '',
required: true,
},
{
name: 'email',
displayName: 'Email',
type: 'email',
defaultValue: '',
},
{
name: 'password',
displayName: 'Password',
type: 'password',
defaultValue: '',
validationHandlerName: ['user', 'passwordValidator']
},
{
name: 'confirmPassword',
displayName: 'Confirm Password',
type: 'password',
defaultValue: '',
validationHandlerName: ['user', 'passwordValidator']
}
],
};
This definition describes:
- the fields in the form
- the field types
- the validation and submission handlers
But it does not contain:
- React components
- validation functions
- API calls
- workflow logic
The schema only describes intent.
The actual behavior is resolved through the registry system.
Separating Data, UI, and Logic
Formitiva intentionally separates forms into three independent concerns:
At a high level, the architecture looks like this:
+----------------------+
| Form Schema |
| (Data Layer) |
+----------------------+
|
v
+----------------------+
| Registry |
| (UI + Logic Mapping) |
+----------------------+
|
v
+----------------------+
| Renderer |
| (React UI) |
+----------------------+
Data (Form Definition)
The schema describes:
- fields
- data types
- constraints
- references to behaviors
It represents the data model of the form, which means it can come from:
- a database
- a backend service
- a CMS
- a visual form builder
- configuration files
Because definitions are pure data, they are portable and easy to version.
UI (Field Components)
The UI layer is responsible only for rendering inputs and collecting user input.
Field components are mapped to field types through the registry.
The schema never references a specific React component.
Logic (Validation and Workflows)
Business logic such as:
- validation rules
- submission workflows
- button actions
is implemented separately and registered with the system.
Schemas only reference logic by name, not by function implementation.
This separation allows forms to evolve without tightly coupling the layers.
What the Registry System Does
The Formitiva registry is a central lookup system that connects schema references to real implementations.
Instead of embedding logic directly inside form definitions or components, you register behaviors once and reference them by name.
The registry supports registering:
- field validation handlers
- form validation handlers
- submission workflows
- button actions
- custom field components
- plugins that bundle multiple extensions
At runtime, Formitiva reads the schema and resolves those references through the registry.
This design is what allows form data, UI, and business logic to remain independent.
Registering Field and Form Validation
Validation is one of the first things that tends to get mixed into UI components.
For example, many form implementations attach validation logic directly to inputs or inside React components.
Formitiva moves validation logic into the registry.
You can register validation handlers like this:
registerFormValidationHandler(
'user:passwordMatch',
passwordMatchValidator
);
Then the schema references those handlers:
{
"validationHandlerName": "user:passwordMatch"
}
This approach has several advantages:
- validation logic is reusable across many forms
- schemas remain simple and declarative
- validation is no longer tied to UI components
The schema only says which validator to use, while the registry provides the implementation.
Registering Submission Workflows
Submission logic is another area where business logic often leaks into UI components.
In many React applications, the form component contains API calls or workflow orchestration inside its submit handler.
Formitiva separates this by registering submission handlers.
registerSubmissionHandler(
'user:userDataSubmission',
userDataSubmissionHandler
);
The schema then references that handler:
{
"submitHandlerName": "user:userDataSubmission"
}
This keeps workflows reusable and independent from the UI.
Submission handlers can perform tasks such as:
- calling APIs
- triggering backend workflows
- saving data
- dispatching events
Most importantly, this business logic no longer lives inside React components.
The form component simply renders the UI, while the submission handler controls what happens after submission.
Registering Custom Field Components
The most powerful extension point in the registry system is custom field types.
Schemas describe what type of data a field represents, but they do not define how the field is rendered.
Instead, the registry maps field types to React components.
Registering a Custom Field Type
For example, you can register a new field type called point2d:
registerComponent('point2d', Point2DInput);
Once registered, any schema field with this type will be rendered using the Point2DInput component.
{
"type": "point2d"
}
This allows schemas to remain UI-agnostic, while the registry determines how each field should appear.
Registering a Type Validator
A field type can also have a type-level validator that checks whether the data matches the expected format.
For example, a built-in int type validator might check:
- whether the value is a valid integer
- whether it satisfies constraints such as min and max
Custom types can register their own validator as well:
registerFieldTypeValidationHandler(
'point2d',
pointValidator
);
The validator ensures that values for the point2d type follow the expected structure (for example [x, y] coordinates).
Registering a type validator is optional. If a custom type does not require additional validation, it can be omitted.
Why This Matters
This design allows you to create domain-specific field types while keeping schemas clean and declarative.
It also makes integration with design systems easier, because schemas only reference field types, not specific UI components.
As a result:
- schemas describe data
- components handle presentation
- validators enforce data correctness
Each concern remains independent, which keeps forms easier to maintain and extend.
Organizing Extensions with Plugins
As your form platform grows, you may want to group related extensions together.
Formitiva supports this through plugins.
A plugin can bundle components, validators, and submission handlers into a single package.
const PointPlugin = {
name: 'point_plugin',
version: '0.1.0',
components: {
point2d: Point2DInput,
},
fieldCustomValidators: {
point2d: {
nonNegativePoint,
},
},
formValidators: {
'point2d:regionValidator': regionValidator,
},
submissionHandlers: {
'point2d:alertSubmission': alertSubmissionHandler,
},
};
registerPlugin(PointPlugin);
This keeps extensions organized instead of scattering logic across different parts of the application.
The Formitiva Architecture
A helpful way to understand Formitiva is to think of it as three layers.
Definition
↓
Registry
↓
Renderer
Definition (Data Layer)
The schema describes the data structure of the form.
It can come from code, APIs, databases, or visual builders.
Registry (Logic & UI Mapping)
The registry maps schema references to implementations such as:
- validators
- workflows
- components
Renderer (UI Layer)
The renderer converts the definition into a working form UI at runtime.
Because of this architecture:
- schemas remain pure data
- UI components stay focused on rendering
- business logic stays reusable
When This Architecture Works Best
The registry-first approach becomes especially valuable when:
- forms are stored in databases or APIs
- many forms exist across a platform
- domain-specific field types are required
- submission workflows are reused
- a visual form builder or low-code system is needed
If your application only has a few static forms, traditional JSX-based forms may be enough.
But once forms become a platform capability rather than just page components, separating UI and business logic becomes much more important.
Final Thoughts
Separating UI and business logic in forms is harder than it first appears.
In many React applications, form components gradually accumulate responsibilities:
- defining data structures
- rendering inputs
- validating data
- calling APIs
- coordinating workflows.
This works for small forms, but it becomes difficult to scale when forms need to be reused or dynamically generated.
Formitiva addresses this with its registry system.
Instead of embedding logic inside form components, Formitiva lets you register validation handlers, submission workflows, and field components separately, while schemas define the data structure and reference those behaviors declaratively.
This separation of data, UI, and logic keeps schemas portable while making UI components and business logic reusable.
And that separation is what turns schema-driven forms from a nice idea into a practical architecture for real applications.
Currently Formitiva supports React forms, while Vue and Angular integrations are currently in testing.
Top comments (0)