react-formbridge
Browse documentation
Hooksv1.0.2

useFormBridgeContext()

Consume the nearest form runtime from context.

  • Use it inside components rendered under <form.Form> when you want to split a form into reusable children without prop drilling
  • Use it with <form.FormProvider> when a toolbar, summary panel, sticky footer, or side layout lives outside the actual form wrapper
  • It returns the same runtime object as useFormBridge(schema) for the nearest provider
  • It throws if used outside <form.Form> or <form.FormProvider>
import {
  field,
  useFormBridge,
  useFormBridgeContext,
} from '@runilib/react-formbridge'

const schema = {
  email: field.email('Email').required(),
  marketingOptIn: field.checkbox('Receive product updates'),
}

function LiveSummary() {
  const { watch, state } = useFormBridgeContext<typeof schema>()
  const email = watch('email')

  return (
    <aside>
      <p>Current email: {email || '-'}</p>
      <p>{state.isValid ? 'Ready to submit' : 'Some required fields are still missing.'}</p>
    </aside>
  )
}

export function NewsletterSettings() {
  const form = useFormBridge(schema, {
    validateOn: 'onBlur',
    revalidateOn: 'onChange',
  })

  return (
    <form.Form onSubmit={async (values) => api.save(values)}>
      <form.fields.email />
      <form.fields.marketingOptIn />
      <LiveSummary />
      <form.Form.Submit>Save preferences</form.Form.Submit>
    </form.Form>
  )
}

const api = new Proxy({}, {
  get: (_target, methodName) => async (values) => {
    console.log('[doc-playground]', String(methodName), values)
    return { methodName, values }
  },
})

export default NewsletterSettings

Use FormProvider outside the form tree

<form.Form> already provides context to everything rendered inside it. Reach for <form.FormProvider> only when a consumer is rendered outside the actual form wrapper but still needs access to submit(), state, watch(), or fieldController().

import {
  field,
  useFormBridge,
  useFormBridgeContext,
} from '@runilib/react-formbridge'

const schema = {
  email: field.email('Email').required(),
  coupon: field.text('Coupon code'),
}

function StickyCheckoutBar() {
  const { state, submit } = useFormBridgeContext<typeof schema>()

  return (
    <footer>
      <p>{state.isValid ? 'Ready to pay' : 'Complete the required fields first'}</p>
      <button
        type="button"
        disabled={state.isSubmitting}
        onClick={() => void submit()}
      >
        Pay now
      </button>
    </footer>
  )
}

export function CheckoutLayout() {
  const form = useFormBridge(schema, {
    persist: { key: 'checkout-layout' },
  })

  return (
    <form.FormProvider>
      <div className="checkout-layout">
        <main>
          <form.Form onSubmit={async (values) => api.checkout(values)}>
            <form.fields.email />
            <form.fields.coupon />
          </form.Form>
        </main>

        <StickyCheckoutBar />
      </div>
    </form.FormProvider>
  )
}

const api = new Proxy({}, {
  get: (_target, methodName) => async (values) => {
    console.log('[doc-playground]', String(methodName), values)
    return { methodName, values }
  },
})

export default CheckoutLayout

Return value

The hook returns the same runtime surface as the nearest useFormBridge(schema) call.

Complete hook return surface. Every value listed below is returned by useFormBridge() in the exact order shown in the code snippet above, with a link to its dedicated reference section:

MethodTypeDescription
FormProviderComponentTypeAdvanced context wrapper for consumers rendered outside <Form>
FormComponentType & { Submit }Generated wrapper component with submit lifecycle (includes Form.Submit)
fieldsRecord<name, Component>Typed generated field components keyed by schema name
FieldErrorComponentType<{ name }>Standalone error renderer for one field name
FieldLabelComponentType<{ name }>Standalone label renderer for one field name
fieldController(name) => ControllerField-scoped runtime for fully custom UI while keeping the schema contract
stateFormStateReactive FormState object (values, errors, touched, dirty, isValid, isSubmitting, …)
visibilityRecord<name, Flags>Per-field visibility / required / disabled state computed from conditional rules
isLoadingDraftbooleantrue while a persisted draft is being restored
hasDraftbooleantrue once a draft was found and restored
clearDraft() => voidDelete the saved draft
saveDraftNow() => voidPersist immediately without waiting for debounce
setValue(name, value) => voidSet one field value programmatically
getValue(name) => valueRead one field value
getValues() => ValuesRead the full value object
validate(names?) => Promise<boolean>Validate one field, several fields, or the full form
resetFields(values?) => voidReset to schema defaults or a provided partial value object
setError(name, message) => voidPush a manual field error
clearErrors(name?) => voidClear one field, many fields, or all errors
watch(name) => valueReactive single-field read
watchAll() => ValuesReactive full-value read
submit() => Promise<void>Imperative submit using the same submit pipeline as Form.Submit

Rules & composition notes

  • The hook must run below the nearest <form.Form> or <form.FormProvider>
  • The nearest provider wins, so nested form providers isolate their own runtime
  • Prefer plain props when one small child needs one value; prefer context when several descendants need coordinated access to the form runtime
  • watch(), watchAll(), state, and visibility remain reactive because the provider shares the live form api object
  • If a consumer needs to trigger submission from outside the rendered form element, <form.FormProvider> is the intended path