Skip to main content

Render React Forms from JSON

Rendering forms is the core of FormEngine. The model is simple:
  1. define a form in JSON
  2. choose a view that maps component types to React components
  3. render the schema with FormViewer
Once that is in place, FormEngine handles the runtime behavior around values, validation, and events.

The render model

FormEngine separates three concerns:
  • the schema describes the form
  • the view maps schema component types to real React components
  • FormViewer renders the schema through that view
This separation is what makes the system flexible. The form definition can stay stable even if your UI layer changes.

Minimal example

This is the smallest useful render setup:
App.tsx
import { useCallback } from 'react'
import { FormViewer } from '@react-form-builder/core'
import { rsView, formEngineRsuiteCssLoader } from '@react-form-builder/components-rsuite'
import contactForm from './contactForm.json'

formEngineRsuiteCssLoader()

export default function App() {
  const getForm = useCallback(() => JSON.stringify(contactForm), [])

  return <FormViewer view={rsView} getForm={getForm} />
}
That is the basic runtime contract:
  • view tells FormEngine how to render component types
  • getForm returns the form JSON
  • FormViewer renders the result

Real working example from the FormEngine examples

The community formdata example uses a real FormViewer setup like this:
From the formdata example
import { viewWithCss } from '@react-form-builder/components-rsuite'
import { FormViewer } from '@react-form-builder/core'
import * as SampleForm from './SampleForm.json'

const view = viewWithCss
const getForm = () => JSON.stringify(SampleForm)

export const FormViewerExample = () => {
  return (
    <FormViewer
      view={view}
      formName="SampleForm"
      getForm={getForm}
      initialData={appLevelState}
      onFormDataChange={onFormDataChanged}
      viewerRef={viewerRef}
      actions={customActions}
    />
  )
}
That example is useful because it is not only rendering a schema. It is also using:
  • initialData
  • onFormDataChange
  • viewerRef
  • custom actions
So it reflects how FormEngine is typically used in a real app rather than only in a minimal demo.

What the form actually looks like

The booking-form example used across the real Next.js, Remix, and Formik samples renders like this: Booking form example The screenshot above reflects the same runtime pattern shown in the code examples on this page.

What FormViewer does

FormViewer is the runtime component that turns your schema into a working form. Its responsibilities include:
  • rendering the component tree
  • managing current form values
  • running validation
  • firing events and actions
  • exposing runtime state through callbacks and refs
That is why most FormEngine workflows start or end at FormViewer.

The most important FormViewer props

PropRequiredPurpose
viewyesmaps schema component types to actual React components
getFormyesreturns the form JSON string
initialDatanoprefills the form with values
onFormDataChangenolets your app react to live changes
viewerRefnogives imperative access to the runtime
actionsnoregisters custom action handlers
languagenocontrols localization
For most pages in this docs set, view and getForm are the essential pair.

What the JSON schema looks like

A form schema usually contains a root Screen component and nested children:
contactForm.json
{
  "errorType": "RsErrorMessage",
  "form": {
    "key": "Screen",
    "type": "Screen",
    "children": [
      {
        "key": "firstName",
        "type": "RsInput",
        "props": {
          "label": { "value": "First name" }
        }
      },
      {
        "key": "lastName",
        "type": "RsInput",
        "props": {
          "label": { "value": "Last name" }
        }
      }
    ]
  }
}
This schema does not render itself. It needs a matching view. Here is a shortened fragment from the real SampleForm.json used by the same example:
Real schema fragment from the formdata example
{
  "errorType": "RsErrorMessage",
  "form": {
    "key": "screen 1",
    "type": "Screen",
    "children": [
      {
        "key": "First name",
        "type": "RsInput",
        "props": {
          "label": { "value": "First name" }
        },
        "schema": {
          "type": "string",
          "validations": [
            { "key": "required" }
          ]
        }
      },
      {
        "key": "Save button",
        "type": "RsButton",
        "props": {
          "children": { "value": "Save" }
        },
        "events": {
          "onClick": [
            { "name": "validate", "type": "common" },
            { "name": "showValidationResult", "type": "code" }
          ]
        }
      }
    ]
  }
}
This is a good example of the full render model in one place:
  • inputs
  • schema validation
  • runtime actions
  • a real submit button flow
If you want a larger working sample with state handling and runtime feedback, use the same viewer, callback, and action patterns shown above and adapt them to your own schema.

What a view does

A view is the mapping layer between schema types like RsInput and real React components from a UI library. FormEngine ships with ready-made views for common stacks:
import { rsView, formEngineRsuiteCssLoader } from '@react-form-builder/components-rsuite'

formEngineRsuiteCssLoader()
If you use your own design system, build a custom view with custom components.

How data, validation, and events fit in

Rendering is only the first layer. Once the form is on screen: That is why rendering is the center of the model, not the whole model.

Common render patterns

Render from a local JSON file

This is the most common way to start:
  • create myForm.json
  • import it into a component
  • return it from getForm

Render with initialData

Use this when a form is editing existing data:
Render with initial data
<FormViewer
  view={rsView}
  getForm={getForm}
  initialData={{
    firstName: 'John',
    lastName: 'Doe'
  }}
/>

Render with runtime hooks

Use this when your app needs more control:
Render with runtime access
<FormViewer
  view={rsView}
  getForm={getForm}
  onFormDataChange={handleDataChange}
  viewerRef={viewerRef}
/>

When to use this page vs quickstart

Use the quickstart when you want the fastest path to a working form. Use this page when you want to understand the render model itself:
  • what FormViewer does
  • how the JSON and view fit together
  • how the runtime layers connect

FAQ

FormViewer expects getForm to return a string, so in practice you usually stringify a JSON object or import a JSON file directly.
Often yes. That is one of the main benefits of separating the schema from the view layer.
A schema, a view, and a FormViewer with getForm.
Read form data, validation, actions and events, or conditional logic depending on which behavior you want to add next.

Next steps

Read form data

Learn how the runtime stores and exposes current values.

Read validation

Add schema-driven rules to the rendered form.

Read actions and events

Connect rendered components to submit and workflow logic.

Try it in the Online Builder

Build a form visually and inspect the exported JSON.