Command Palette

Search for a command to run...

TreegeRenderer

The TreegeRenderer component transforms workflow into interactive forms with built-in validation and security features.

Import

import { TreegeRenderer } from "treege/renderer"

For React Native applications, use:

import { TreegeRenderer } from "treege/renderer-native"

Props

flow (optional)

The flow data to render as a form.

flow?: Flow | null
  • If a Flow: renders that flow as a form
  • If null or undefined: renders nothing

onSubmit (optional)

Callback function called when the form is submitted.

onSubmit?: (values: FormValues, meta?: Meta) => void

Parameters:

  • values: Form values (keyed by field name or node ID)
  • meta: Optional metadata about the submission (e.g., HTTP response data)

onChange (optional)

Callback called whenever form values change.

onChange?: (values: FormValues) => void

extraPayload (optional)

Extra data, owned by the host app rather than the form, attached to every submission — e.g. the logged-in user id or a tenant id. The fields are merged at the top level of both the onSubmit payload and the body of the built-in HTTP submit, so they ride along wherever the form data goes.

extraPayload?: Record<string, unknown> | ((values: FormValues) => Record<string, unknown>)

Pass a static object for a value already known at render time:

<TreegeRenderer flow={flow} extraPayload={{ userId }} onSubmit={handleSubmit} />
// onSubmit receives → { ...formValues, userId }

Pass a function when the value must be read at submit time rather than captured at render — a rotating auth token, a timestamp, or a value derived from the current answers. It receives the same name-keyed values you get in onSubmit:

<TreegeRenderer flow={flow} extraPayload={() => ({ token: auth.getToken() })} />
<TreegeRenderer flow={flow} extraPayload={(values) => ({ fullName: `${values.firstName} ${values.lastName}` })} />

Notes:

  • The extra fields are spread last, so a key here intentionally overrides a same-named form field.
  • For the built-in HTTP submit (submitConfig), the fields are merged into the request body, respecting any payloadTemplate shape. If the config would otherwise send no body, the extra is sent on its own — your data is never silently dropped.
  • A function that returns a non-object (array / primitive) is ignored.

theme (optional)

Theme for the rendered form.

theme?: "light" | "dark"

Default: "dark"

language (optional)

Current language for translations.

language?: string

Default: "en"

validationMode (optional)

When to trigger validation.

validationMode?: "onSubmit" | "onChange"

Default: "onSubmit"

initialValues (optional)

Initial values for the form fields — use this to pre-fill the form when editing a record that was already submitted.

initialValues?: FormValues

Keys can be either node.id OR the same name-based keys you receive back in onChange / onSubmit, so the previously-submitted object can be fed straight back in without any remapping:

const handleSubmit = (values) => save(values)        // e.g. { firstName: "Alice" }
// later, to edit:
<TreegeRenderer flow={flow} initialValues={savedRecord} />  // same object works as-is

Reactive: if initialValues changes after mount (e.g. an async-fetched record resolves later), the form is re-seeded automatically — no need for a key prop or isLoading gate. A new object literal of identical content does not reset the form, so in-progress user edits are preserved.

const { data } = useQuery(/* ... */)            // resolves asynchronously
<TreegeRenderer flow={flow} initialValues={data ?? {}} />  // fills in once data arrives

Pre-filling file fields: A file field's value is a serializable object (never a DOM File, so it round-trips through JSON) — a single object, an array when the field is multiple, or null:

type SerializableFile = {
  name: string;
  size: number;
  type: string;
  lastModified: number;
  data: string; // base64 data-URL (web) or file URI (native)
};

Pass the same shape in initialValues to pre-fill a file field when editing — the renderer lists the files and lets the user remove them or add more. Only name / size are used for display, so data can hold a server URL for already-uploaded files:

<TreegeRenderer
  flow={flow}
  initialValues={{
    attachment: { name: "contract.pdf", size: 24576, type: "application/pdf", lastModified: 0, data: "https://cdn.example.com/contract.pdf" },
  }}
/>

components (optional)

Custom component renderers. Override any of: inputs, inputLabel (the shared field-label component), ui, form, step, submitButton, submitButtonWrapper, loadingSkeleton.

components?: TreegeRendererComponents

See Customization for details.

validate (optional)

Custom validation function.

validate?: (values: FormValues, nodes: Node<TreegeNodeData>[]) => Record<string, string>

Parameters:

  • values: Current form values
  • nodes: All visible nodes in the tree

Returns: Object mapping node IDs to error messages

googleApiKey (optional)

Google Maps API key for address autocomplete.

googleApiKey?: string

If not provided, falls back to free Nominatim (OpenStreetMap).

headers (optional)

Global HTTP headers applied to every request issued by the renderer (HTTP inputs, submit buttons). Field-level headers with the same key (case-insensitive) take precedence.

headers?: HttpHeaders // Record<string, string>, e.g. { Authorization: "Bearer ..." }

isLoading (optional)

When true, renders a loading skeleton instead of the form — useful while the flow is being fetched. Customize the skeleton via components.loadingSkeleton.

isLoading?: boolean

Default: false

<TreegeRenderer flow={flow ?? null} isLoading={isPending} onSubmit={handleSubmit} />

isSubmitting (optional)

When true, forces the submit/continue button into its loading state (spinner + disabled). Use this to keep the button busy while an async onSubmit is still resolving on your side.

isSubmitting?: boolean

Default: false

It is OR-ed with the renderer's own internal submitting state (e.g. during an HTTP submitConfig call), so it only ever adds to the built-in behavior.

<TreegeRenderer flow={flow} isSubmitting={mutation.isPending} onSubmit={handleSubmit} />

formId (optional)

Sets the id attribute on the underlying <form> element, so a submit button rendered outside the renderer can target it via the native HTML form attribute. Web only.

formId?: string
<TreegeRenderer flow={flow} formId="my-form" onSubmit={handleSubmit} />
 
// anywhere else on the page — even outside the renderer:
<button type="submit" form="my-form">Save</button>

In a multi-step flow this external button (and pressing Enter in a field) only triggers the real submission on the final step (path complete) — on earlier steps it advances to the next step, mirroring the built-in Continue button.

onBack (optional)

Called when the user clicks Back on the first step. Provide this to bridge back-navigation to an outer flow — e.g. stepping back in a parent modal that embeds the renderer.

onBack?: () => void

When set, a Back button is shown on the first step and triggers this callback instead of being a no-op. It has no effect on later steps, which always navigate back internally.

<TreegeRenderer flow={flow} onBack={() => modal.goToPreviousStep()} onSubmit={handleSubmit} />

title (optional)

Form title rendered above the form. Works on both web and React Native.

title?: string | Translatable

Accepts either a plain string or a Translatable object (keyed by language code), resolved with the current language:

<TreegeRenderer flow={flow} title="Contact form" />
 
// Multi-language
<TreegeRenderer
  flow={flow}
  title={{ en: "Contact form", fr: "Formulaire de contact" }}
  language="fr"
/>

className (optional)

Additional CSS class names applied to the renderer container.

className?: string

style (optional)

Inline styles applied to the renderer's root container, alongside className. Web only.

style?: CSSProperties
<TreegeRenderer flow={flow} style={{ maxWidth: 480 }} />

disableSectionBorder (optional)

Removes the default border of the <section> wrapping each step — padding and layout are kept, only the border disappears. Useful when the renderer is embedded in a container that already has its own frame (card, modal…). Web only.

disableSectionBorder?: boolean
<TreegeRenderer flow={flow} disableSectionBorder />

Multi-Step Forms

When a flow contains Group nodes, the renderer automatically splits the form into navigable steps with Back/Continue controls. You can override the step layout via components.step — see Customization.

Continue becomes Submit only on the final step — when the path is actually complete (end of path reached, or an explicit submit input is visible). On a branching boundary step (the last step currently visible while the flow still continues behind an unanswered choice), the button stays a disabled Continue, and pressing Enter never submits — an incomplete flow can't be sent.

Auto-advance: when a step's only interactive field is a single-choice input (radio, non-multiple select, autocomplete, or an HTTP input mapped to options), selecting an option automatically advances to the next step after a short delay (~180 ms). It only triggers on a direct user selection — never on mount, when navigating back to an answered step, or on the final step (a choice never submits silently).

Two props help integrate multi-step flows into a surrounding app:

  • onBack — bridges the Back button on the first step to an outer flow (e.g. a parent modal). See the onBack prop.
  • formId — lets a submit button rendered outside the renderer drive submission; in a multi-step flow it only submits on the final step, advancing otherwise. See the formId prop.

Headless Usage

For full programmatic control, use the useTreegeRenderer hook directly. It accepts the same configuration as TreegeRenderer and returns the form state and control methods (formValues, setFieldValue, handleSubmit, step navigation, etc.):

import { useTreegeRenderer } from "treege/renderer"
 
const { formValues, setFieldValue, handleSubmit, isSubmitting } = useTreegeRenderer({
  flow: flow,
  onSubmit: (values) => console.log(values),
})

The hook also exposes the full step machinery, so a custom UI gets the exact same behavior as the built-in renderers:

  • handleContinue / handleBack — ready-made action handlers (Continue submits only on the final step; Back bridges to the onBack prop on the first step).
  • canContinue / canGoBack — button gating (required fields filled, a next step or a complete path exists).
  • isFinalSteptrue only when the path is complete; drives the Continue vs Submit label. Unlike isLastStep, it stays false on a branching boundary step.
  • stepLabel / formTitle — translated label of the current group and of the title prop.

The return type is exported as UseTreegeRendererReturn.

Global Configuration

Wrap your app with TreegeRendererProvider to define default options shared by every TreegeRenderer below it — instead of repeating the same props on each instance. It accepts the same configuration fields as TreegeRenderer (language, theme, googleApiKey, headers, baseUrl, validationMode, components):

import { TreegeRendererProvider, TreegeRenderer } from "treege/renderer"
 
function App() {
  return (
    <TreegeRendererProvider
      language="fr"
      theme="light"
      googleApiKey="your-google-api-key"
    >
      {/* Every renderer below inherits the provider's config */}
      <TreegeRenderer flow={flowA} onSubmit={handleA} />
      <TreegeRenderer flow={flowB} onSubmit={handleB} />
    </TreegeRendererProvider>
  )
}

Props always take precedence over the provider, which takes precedence over the built-in defaults (language: "en", theme: "dark", validationMode: "onSubmit"). So you can set an app-wide default and override it on a single instance:

<TreegeRendererProvider language="fr">
  <TreegeRenderer flow={flow} />               {/* renders in French   */}
  <TreegeRenderer flow={flow} language="en" /> {/* overridden to English */}
</TreegeRendererProvider>

components.inputs / components.ui are merged field-by-field (provider + instance), and headers from the provider and the instance are combined — so a renderer can add or override a single entry without losing the shared ones.

The provider is optional: used outside it, TreegeRenderer falls back to its props and the built-in defaults. You can also read the resolved global config with the useTreegeRendererConfig hook.

Security Features

XSS Protection

TreegeRenderer includes built-in protection against Cross-Site Scripting (XSS) attacks using DOMPurify. All user input is automatically sanitized before being rendered to prevent malicious code injection.

Key Features:

  • Automatic HTML sanitization for all text inputs
  • Protection against script injection
  • Safe rendering of user-generated content
  • No configuration required - works out of the box

This ensures that even if users input malicious HTML or JavaScript code, it will be safely sanitized before rendering.

// User input with potential XSS
const maliciousInput = "<script>alert('XSS')</script>";
 
// TreegeRenderer automatically sanitizes this
<TreegeRenderer
  flow={flow}
  initialValues={{ comment: maliciousInput }}
/>
// Output: Safe, sanitized text without the script tag

Example

Basic Usage

<TreegeRenderer
  flow={flow}
  theme="dark"
  language="en"
  validationMode="onSubmit"
  onSubmit={(values, meta) => {
    console.log("Form values:", values)
    console.log("Metadata:", meta)
  }}
  onChange={(values) => setFormValues(values)}
  initialValues={{ name: "John", email: "john@example.com" }}
/>

With Custom Components and Validation

<TreegeRenderer
  flow={flow}
  theme="dark"
  onSubmit={(values) => console.log(values)}
  components={{
    inputs: {
      text: CustomTextInput,
      email: CustomEmailInput,
    },
    submitButton: ({ label }) => (
      <button type="submit" className="btn-primary">
        {label || "Submit"}
      </button>
    ),
  }}
  validate={(values, nodes) => {
    const errors: Record<string, string> = {}
 
    if (values.age && values.age < 18) {
      errors.age = "Must be 18 or older"
    }
 
    return errors
  }}
  googleApiKey={process.env.GOOGLE_MAPS_API_KEY}
/>