Skip to main content

Build Your First React Form with FormEngine

This guide takes the next step after the quickstart. Instead of showing only the minimum setup, it walks through a slightly more complete form so you can see how FormEngine handles structure, validation, rendering, and submission-ready data in one workflow. This is the first complete authoring-to-runtime flow: First form example

What you are building

You will build a simple contact form with:
  • a required name field
  • a required email field with email validation
  • an optional message field
  • a submit button
The goal is not just to render a form. The goal is to understand the basic FormEngine model well enough to keep extending it.

Why this approach is useful

In a traditional React form, you often define fields, validation, and behavior inside components and local state. FormEngine changes that shape. The form becomes a JSON definition that the runtime renders and manages for you. That matters because once a form grows, JSON is often easier to:
  • reuse across flows
  • review in code
  • extend with validation and conditional behavior
  • connect to a visual builder later

Step 1: Define the form schema

Create a file named contactForm.json:
contactForm.json
{
  "actions": {
    "submitForm": {
      "body": "console.log('Submitted data', e.data)",
      "params": {}
    }
  },
  "errorType": "RsErrorMessage",
  "form": {
    "key": "Screen",
    "type": "Screen",
    "children": [
      {
        "key": "contactStack",
        "type": "RsContainer",
        "children": [
          {
            "key": "name",
            "type": "RsInput",
            "props": {
              "label": { "value": "Your name" },
              "placeholder": { "value": "Enter your full name" }
            },
            "schema": {
              "validations": [
                { "key": "required", "args": { "message": "Name is required" } }
              ]
            }
          },
          {
            "key": "email",
            "type": "RsInput",
            "props": {
              "label": { "value": "Email" },
              "placeholder": { "value": "you@example.com" }
            },
            "schema": {
              "validations": [
                { "key": "required", "args": { "message": "Email is required" } },
                { "key": "email", "args": { "message": "Enter a valid email" } }
              ]
            }
          },
          {
            "key": "message",
            "type": "RsTextArea",
            "props": {
              "label": { "value": "Message" },
              "rows": { "value": 4 }
            }
          },
          {
            "key": "submit",
            "type": "RsButton",
            "props": {
              "children": { "value": "Send message" },
              "appearance": { "value": "primary" }
            },
            "events": {
              "onClick": [
                {
                  "name": "validate",
                  "type": "common",
                  "args": {
                    "failOnError": true
                  }
                },
                {
                  "name": "submitForm",
                  "type": "code"
                }
              ]
            }
          }
        ]
      }
    ]
  }
}

Step 2: Render it with FormViewer

Use FormViewer to render the schema:
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} />
}
At this point, the form is already functional. The viewer reads the JSON, renders the fields, and runs the validation rules declared in the schema.

Step 3: Understand the important parts

Here is what matters in the schema:
  • form defines the component tree
  • type selects which component to render
  • props configure labels, placeholders, and UI behavior
  • schema.validations defines validation rules
  • events connect user interaction to actions
  • actions contain reusable runtime logic
This is the core idea of FormEngine. The form is data, not just JSX.

Step 4: Observe form data changes

You will usually want to inspect the current form state while building:
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), [])

  const handleDataChange = useCallback((formData) => {
    console.log('Current data', formData.data)
    console.log('Current errors', formData.errors)
  }, [])

  return (
    <FormViewer
      view={rsView}
      getForm={getForm}
      onFormDataChange={handleDataChange}
    />
  )
}
This makes it easier to debug validation and understand what the runtime is doing as users interact with the form.

What to improve next

Once this first form works, most teams move in one of these directions:

FAQ

The quickstart is the fastest path to a working form. This page goes one step further and explains the moving parts of a more complete first example.
No. Start by recognizing the main sections like form, props, schema, events, and actions. You can build from there.
Only when you need them. Simple forms can start with rendering and validation, then add actions when submission or custom behavior becomes important.

Next steps

Understand validation

Learn how to add richer rules and error handling.

Add conditional logic

Make the form react to user input with dynamic behavior.

Build a multi-step form

Move from a single-screen form to a fuller workflow.

Try the Online Builder

Create a similar form visually and export the JSON.