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

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 last step — 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} />

className (optional)

Additional CSS class names applied to the renderer container.

className?: string

Multi-Step Forms

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

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 last 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 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}
/>