Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/rijvi-mahmud/shaddy/llms.txt

Use this file to discover all available pages before exploring further.

When ShaddyForm renders, it wraps its children with react-hook-form’s Form provider (the shadcn/ui wrapper around FormProvider), making the full form instance available throughout the component tree. Field components tap into this context to register themselves, display validation errors, and read current values — all without receiving any form props directly from their parent.

How FormContext Works

ShaddyForm calls useForm() internally and renders <Form {...form}>, which is the shadcn/ui Form component that wraps react-hook-form’s FormProvider. Any component inside ShaddyForm can therefore call useFormContext() to access the full UseFormReturn object — including control, register, getValues, setValue, formState, and more. The form-context.ts file also exports a narrower typed context and hook for scenarios where you want to build utilities that only depend on the control object:
export type ShaddyFormContext<TFormValues extends FieldValues = any> = {
  control: Control<TFormValues>;
};

export const useShaddyFormContext = <TFormValues extends FieldValues = any>() => {
  const context = useContext(ShaddyFormContext);
  if (!context) {
    throw new Error("Form fields must be used within a SmartForm.");
  }
  return context as ShaddyFormContext<TFormValues>;
};
useShaddyFormContext reads from a separate ShaddyFormContext that is not automatically provided by ShaddyForm. To use it, you must render ShaddyFormContext.Provider yourself. For most use cases — including all built-in field components — use react-hook-form’s useFormContext() instead, which works out of the box inside any ShaddyForm.

useFormContext() for Custom Fields

The primary way to build a custom field is to call useFormContext<T>() inside your component. This gives you the control object to pass to FormField, as well as any other form method you need.
import { FieldValues, Path, useFormContext } from "react-hook-form";
import {
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";

type TextFieldProps<T extends FieldValues> = {
  name: Path<T>;
  label?: string;
  placeholder?: string;
  required?: boolean;
};

export const TextField = <T extends FieldValues>({
  name,
  label,
  placeholder,
  required,
}: TextFieldProps<T>) => {
  const { control } = useFormContext<T>(); // ← reads from ShaddyForm's context

  return (
    <FormField
      control={control}
      name={name}
      render={({ field }) => (
        <FormItem>
          {label && (
            <FormLabel>
              {label}
              {required && <span className="text-red-500 ml-1">*</span>}
            </FormLabel>
          )}
          <FormControl>
            <Input placeholder={placeholder} {...field} />
          </FormControl>
          <FormMessage />
        </FormItem>
      )}
    />
  );
};
Because useFormContext reads from the nearest form provider, this component works anywhere inside a ShaddyForm regardless of how deeply nested it is — no prop drilling required.

Accessing Form Values in Non-Field Components

You can use useFormContext in any component inside ShaddyForm, not just fields. For example, a review step in a multi-step form can read submitted values without triggering re-registration:
import { useFormContext } from "react-hook-form";
import type { FormValues } from "./schema";

export const FormSummary = () => {
  const { getValues } = useFormContext<FormValues>();
  const values = getValues();

  return (
    <div className="space-y-2">
      <p><strong>Name:</strong> {values.name}</p>
      <p><strong>Email:</strong> {values.email}</p>
      <p><strong>Country:</strong> {values.country}</p>
    </div>
  );
};
Place this component as a child of ShaddyForm and it will automatically receive the live form data.

useTriggerForm — Programmatic Step Validation

useTriggerForm is a custom hook designed for multi-step forms. It wraps react-hook-form’s form.trigger() method in a convenience function that accepts the form instance and an array of field paths to validate, returning a structured result object.
export const useTriggerForm = <T extends FieldValues>() => {
  const triggerForm = async (
    form: UseFormReturn<T> | null | undefined,
    paths: Path<T>[]
  ) => {
    if (!form) {
      return { hasError: true, message: "Form is not initialized" };
    }

    const triggerResults = await form.trigger(paths);

    if (triggerResults === false) {
      return { hasError: true, message: "Please fill in all fields" };
    }

    return { hasError: false };
  };

  return triggerForm;
};

Return Value

hasError
boolean
true if any of the specified fields failed validation, false if all fields are valid.
message
string | undefined
A human-readable error message when hasError is true. This is a generic message, not the field-level error — individual field errors are displayed by each field’s FormMessage component.

Usage in a Multi-Step Form

import { useRef } from "react";
import { ShaddyForm, ShaddyFormRef } from "@/components/form/shaddy-form";
import { useTriggerForm } from "@/components/form/use-trigger-form";
import { Stepper, Step } from "@/components/ui/stepper";
import type { FormValues } from "./schema";

export function MultiStepForm() {
  const formRef = useRef<ShaddyFormRef<FormValues>>(null);
  const triggerForm = useTriggerForm<FormValues>();

  return (
    <ShaddyForm<FormValues>
      ref={formRef}
      schema={formSchema}
      initialValues={initialValues}
      onSubmit={(data) => console.log("Done:", data)}
    >
      <Stepper>
        <Step
          validate={() =>
            triggerForm(formRef.current?.form, ["name", "email"])
          }
        >
          {/* Step 1 fields */}
        </Step>
        <Step
          validate={() =>
            triggerForm(formRef.current?.form, ["address", "country"])
          }
        >
          {/* Step 2 fields */}
        </Step>
      </Stepper>
    </ShaddyForm>
  );
}
The Step component’s validate prop expects a function that returns { hasError: boolean }. When hasError is true, the stepper stays on the current step and each field displays its own validation error message automatically.
useTriggerForm requires access to the ShaddyFormRef — it does not use useFormContext. This is intentional: it is designed to be called from outside the form context (e.g., in a parent component or a stepper controller), not from within a field component.

Build docs developers (and LLMs) love