Graneet Form LogoGraneet form

useFormContext

Hook for accessing form context within form components

useFormContext Hook

The useFormContext hook provides access to the form context API from within any component that's rendered inside a <Form> component. This is useful for creating reusable form components that need access to form state and methods.

Usage

import { useFormContext } from 'graneet-form';

const formContext = useFormContext<FormDataType>();

Parameters

  • Generic type parameter T extends FieldValues - The type of your form data

Returns

Returns the same FormContextApi<T> object as returned by useForm:

Prop

Type

Examples

Custom Form Component

interface UserForm {
  firstName: string;
  lastName: string;
  email: string;
}

function CustomFormActions() {
  const form = useFormContext<UserForm>();
  
  const handleReset = () => {
    form.resetForm();
  };

  const handleSubmit = form.handleSubmit((data) => {
    console.log('Form submitted:', data);
  });

  return (
    <div className="form-actions">
      <button type="button" onClick={handleReset}>
        Reset Form
      </button>
      <button type="submit" onClick={handleSubmit}>
        Submit
      </button>
    </div>
  );
}

// Usage in form
function MyForm() {
  const form = useForm<UserForm>();

  return (
    <Form form={form}>
      <Field name="firstName" />
      <Field name="lastName" />
      <Field name="email" />
      <CustomFormActions />
    </Form>
  );
}

Form Status Display

function FormStatusDisplay() {
  const form = useFormContext<UserForm>();
  const { formStatus, isValid } = useFormStatus(form);

  return (
    <div className="form-status">
      <span className={`status ${formStatus}`}>
        Status: {formStatus}
      </span>
      <span className={`validity ${isValid ? 'valid' : 'invalid'}`}>
        {isValid ? '✓ Valid' : '✗ Invalid'}
      </span>
    </div>
  );
}

Field Value Display

function FormPreview() {
  const form = useFormContext<UserForm>();
  const values = useFieldsWatch(form, undefined);

  return (
    <div className="form-preview">
      <h3>Form Preview</h3>
      <pre>{JSON.stringify(values, null, 2)}</pre>
    </div>
  );
}

Custom Field with Form Integration

interface CustomFieldProps {
  name: keyof UserForm;
  label: string;
  placeholder?: string;
}

function CustomField({ name, label, placeholder }: CustomFieldProps) {
  const form = useFormContext<UserForm>();
  const validations = useValidations(form, [name]);
  const validation = validations[name];

  return (
    <div className="custom-field">
      <label htmlFor={name}>{label}</label>
      
      <Field name={name}>
        {(props, state) => (
          <input
            {...props}
            id={name}
            placeholder={placeholder}
            className={validation?.status === 'invalid' ? 'error' : ''}
          />
        )}
      </Field>

      {validation?.status === 'invalid' && (
        <span className="error-message">{validation.message}</span>
      )}
    </div>
  );
}

Conditional Form Logic

function ConditionalFields() {
  const form = useFormContext<UserForm>();
  const { accountType, country } = useFieldsWatch(form, ['accountType', 'country']);

  return (
    <div>
      <Field name="accountType" />
      <Field name="country" />

      {accountType === 'business' && (
        <>
          <Field name="companyName" />
          <Field name="taxId" />
        </>
      )}

      {country === 'US' && (
        <Field name="ssn" />
      )}
    </div>
  );
}

Form Auto-Save Component

function AutoSave() {
  const form = useFormContext<UserForm>();
  const values = useFieldsWatch(form, undefined, { mode: 'onBlur' });
  const [lastSaved, setLastSaved] = useState<Date | null>(null);

  useEffect(() => {
    if (Object.keys(values).length > 0) {
      const timer = setTimeout(async () => {
        await saveFormDraft(values);
        setLastSaved(new Date());
      }, 1000);

      return () => clearTimeout(timer);
    }
  }, [values]);

  return (
    <div className="auto-save">
      {lastSaved && (
        <span>✓ Auto-saved at {lastSaved.toLocaleTimeString()}</span>
      )}
    </div>
  );
}

Form Navigation Component

function FormNavigation() {
  const form = useFormContext<UserForm>();
  const { isValid } = useFormStatus(form);
  const values = form.getFormValues();

  const goToStep = (step: string) => {
    // Save current form state before navigation
    localStorage.setItem('formDraft', JSON.stringify(values));
    router.push(`/form/${step}`);
  };

  return (
    <div className="form-navigation">
      <button 
        onClick={() => goToStep('step1')}
        className={values.firstName ? 'completed' : 'pending'}
      >
        Step 1: Personal Info
      </button>
      
      <button 
        onClick={() => goToStep('step2')}
        disabled={!values.firstName}
        className={values.email ? 'completed' : 'pending'}
      >
        Step 2: Contact Info
      </button>
      
      <button 
        onClick={() => goToStep('step3')}
        disabled={!isValid}
        className={isValid ? 'ready' : 'disabled'}
      >
        Step 3: Review
      </button>
    </div>
  );
}

Form Error Summary

function FormErrorSummary() {
  const form = useFormContext<UserForm>();
  const validations = useValidations(form, undefined);

  const errors = Object.entries(validations)
    .filter(([_, validation]) => validation?.status === 'invalid')
    .map(([fieldName, validation]) => ({
      field: fieldName,
      message: validation!.message
    }));

  if (errors.length === 0) return null;

  return (
    <div className="error-summary">
      <h4>Please fix the following errors:</h4>
      <ul>
        {errors.map(({ field, message }) => (
          <li key={field}>
            <button
              type="button"
              onClick={() => {
                const fieldElement = document.querySelector(`[name="${field}"]`);
                fieldElement?.focus();
              }}
            >
              {field}: {message}
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Form Progress Indicator

function FormProgress() {
  const form = useFormContext<UserForm>();
  const values = form.getFormValues();
  const { isValid } = useFormStatus(form);

  const requiredFields = ['firstName', 'lastName', 'email'];
  const completedFields = requiredFields.filter(field => values[field]);
  const progress = (completedFields.length / requiredFields.length) * 100;

  return (
    <div className="form-progress">
      <div className="progress-bar">
        <div 
          className="progress-fill"
          style={{ width: `${progress}%` }}
        />
      </div>
      <span className="progress-text">
        {completedFields.length} of {requiredFields.length} required fields completed
        {isValid && ' ✓'}
      </span>
    </div>
  );
}

Context Requirements

The useFormContext hook must be used within a component tree that has a <Form> component as an ancestor:

// ✅ Correct usage
function MyForm() {
  const form = useForm<UserForm>();

  return (
    <Form form={form}>
      <MyFormComponent /> {/* Can use useFormContext */}
    </Form>
  );
}

// ❌ Incorrect usage - will throw error
function MyFormComponent() {
  const form = useFormContext<UserForm>(); // Error: No form context found
  // ...
}

Performance Notes

  • The hook provides direct access to the same form context passed to <Form>
  • No additional subscriptions or state management overhead
  • Efficient for creating reusable form components
  • All form methods are memoized for optimal performance