npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@manifesto-ai/bridge-react-hook-form

v0.3.0

Published

React Hook Form bridge for Manifesto AI

Readme

@manifesto-ai/bridge-react-hook-form

React Hook Form integration for Manifesto AI Bridge

Seamlessly connect React Hook Form with Manifesto runtime using a convenient React hook.

Installation

pnpm add @manifesto-ai/bridge-react-hook-form @manifesto-ai/bridge @manifesto-ai/core react-hook-form

Quick Start

import { useForm } from 'react-hook-form';
import { useMemo } from 'react';
import { createRuntime, defineDomain, defineDerived, z } from '@manifesto-ai/core';
import { useManifestoBridge, executeAction } from '@manifesto-ai/bridge-react-hook-form';

// Define domain
const formDomain = defineDomain('signup', {
  dataSchema: z.object({
    email: z.string().email(),
    password: z.string().min(8),
    confirmPassword: z.string()
  }),

  derived: {
    'derived.passwordsMatch': defineDerived(
      { $eq: [{ $get: 'data.password' }, { $get: 'data.confirmPassword' }] },
      z.boolean()
    ),
    'derived.canSubmit': defineDerived(
      {
        $and: [
          { $gt: [{ $size: { $get: 'data.email' } }, 0] },
          { $gte: [{ $size: { $get: 'data.password' } }, 8] },
          { $get: 'derived.passwordsMatch' }
        ]
      },
      z.boolean()
    )
  }
});

function SignupForm() {
  const form = useForm({
    defaultValues: {
      email: '',
      password: '',
      confirmPassword: ''
    }
  });

  const runtime = useMemo(() => createRuntime(formDomain), []);
  const bridge = useManifestoBridge(form, runtime);

  const handleSubmit = form.handleSubmit(async (data) => {
    if (bridge.isActionAvailable('submit')) {
      await bridge.execute(executeAction('submit'));
    }
  });

  return (
    <form onSubmit={handleSubmit}>
      <input {...form.register('email')} placeholder="Email" />
      <input {...form.register('password')} type="password" placeholder="Password" />
      <input {...form.register('confirmPassword')} type="password" placeholder="Confirm Password" />

      <button type="submit" disabled={!bridge.isActionAvailable('submit')}>
        Sign Up
      </button>
    </form>
  );
}

API Reference

useManifestoBridge(form, runtime, options?)

React hook that creates and manages a Manifesto Bridge with React Hook Form.

interface UseManifestoBridgeOptions {
  // Sync direction: 'push', 'pull', or 'bidirectional' (default)
  syncMode?: 'push' | 'pull' | 'bidirectional';

  // Auto-sync when form values change (default: true)
  autoSync?: boolean;

  // Debounce sync in milliseconds (default: 0)
  debounceMs?: number;
}

Returns: A Bridge instance with the following methods:

interface Bridge {
  // Execute a command
  execute(command: Command): Promise<Result<void>>;

  // Check if an action is available
  isActionAvailable(actionId: string): boolean;

  // Subscribe to state changes
  subscribe(listener: (snapshot) => void): () => void;

  // Get current snapshot
  getSnapshot(): { data: unknown; state: unknown };

  // Cleanup
  dispose(): void;
}

Example:

function MyForm() {
  const form = useForm();
  const runtime = useMemo(() => createRuntime(domain), []);

  const bridge = useManifestoBridge(form, runtime, {
    syncMode: 'bidirectional',
    autoSync: true,
    debounceMs: 100  // Debounce syncs by 100ms
  });

  // Bridge automatically syncs form values with runtime
  // and validates using domain rules
}

createReactHookFormAdapter(form)

Creates an adapter that reads from React Hook Form.

const adapter = createReactHookFormAdapter(form);

// Reads form values as 'data.*' paths
adapter.getData('data.email');  // Returns form field value

// Gets validation errors as ValidationResult
adapter.getValidity('data.email');
// { valid: false, issues: [{ code: 'email', message: 'Invalid email', ... }] }

// Captures all form data
adapter.captureData();
// { 'data.email': '[email protected]', 'data.password': '...' }

createReactHookFormActuator(form)

Creates an actuator that writes to React Hook Form.

const actuator = createReactHookFormActuator(form);

// Sets form field value
actuator.setData('data.email', '[email protected]');
// Triggers validation, marks as dirty and touched

// Focus a field
actuator.focus('data.email');

// Set multiple values at once
actuator.setManyData({
  'data.firstName': 'John',
  'data.lastName': 'Doe'
});

Full Example: Multi-Step Form

import { useForm } from 'react-hook-form';
import { useMemo, useState, useEffect } from 'react';
import { createRuntime, defineDomain, defineDerived, defineAction, setValue, z } from '@manifesto-ai/core';
import { useManifestoBridge, executeAction } from '@manifesto-ai/bridge-react-hook-form';

const wizardDomain = defineDomain('wizard', {
  dataSchema: z.object({
    // Step 1: Personal Info
    firstName: z.string(),
    lastName: z.string(),
    email: z.string().email(),
    // Step 2: Address
    street: z.string(),
    city: z.string(),
    zipCode: z.string(),
    // Step 3: Payment
    cardNumber: z.string(),
    expiryDate: z.string()
  }),

  stateSchema: z.object({
    currentStep: z.number().default(1),
    isSubmitting: z.boolean().default(false)
  }),

  derived: {
    'derived.step1Complete': defineDerived(
      {
        $and: [
          { $gt: [{ $size: { $get: 'data.firstName' } }, 0] },
          { $gt: [{ $size: { $get: 'data.lastName' } }, 0] },
          { $test: [{ $get: 'data.email' }, '^[^@]+@[^@]+\\.[^@]+$'] }
        ]
      },
      z.boolean()
    ),
    'derived.step2Complete': defineDerived(
      {
        $and: [
          { $gt: [{ $size: { $get: 'data.street' } }, 0] },
          { $gt: [{ $size: { $get: 'data.city' } }, 0] },
          { $gt: [{ $size: { $get: 'data.zipCode' } }, 0] }
        ]
      },
      z.boolean()
    ),
    'derived.canProceed': defineDerived(
      {
        $if: [
          { $eq: [{ $get: 'state.currentStep' }, 1] },
          { $get: 'derived.step1Complete' },
          { $if: [
            { $eq: [{ $get: 'state.currentStep' }, 2] },
            { $get: 'derived.step2Complete' },
            true
          ]}
        ]
      },
      z.boolean()
    )
  },

  actions: {
    nextStep: defineAction({
      precondition: {
        $and: [
          { $get: 'derived.canProceed' },
          { $lt: [{ $get: 'state.currentStep' }, 3] }
        ]
      },
      effect: setValue('state.currentStep', {
        $add: [{ $get: 'state.currentStep' }, 1]
      })
    }),
    prevStep: defineAction({
      precondition: { $gt: [{ $get: 'state.currentStep' }, 1] },
      effect: setValue('state.currentStep', {
        $subtract: [{ $get: 'state.currentStep' }, 1]
      })
    })
  }
});

function WizardForm() {
  const form = useForm({
    defaultValues: {
      firstName: '', lastName: '', email: '',
      street: '', city: '', zipCode: '',
      cardNumber: '', expiryDate: ''
    }
  });

  const runtime = useMemo(() => createRuntime(wizardDomain), []);
  const bridge = useManifestoBridge(form, runtime);

  const [currentStep, setCurrentStep] = useState(1);
  const [canProceed, setCanProceed] = useState(false);

  useEffect(() => {
    const unsub1 = runtime.subscribe('state.currentStep', setCurrentStep);
    const unsub2 = runtime.subscribe('derived.canProceed', setCanProceed);
    return () => { unsub1(); unsub2(); };
  }, [runtime]);

  const handleNext = async () => {
    await bridge.execute(executeAction('nextStep'));
  };

  const handlePrev = async () => {
    await bridge.execute(executeAction('prevStep'));
  };

  return (
    <form>
      {currentStep === 1 && (
        <div>
          <h2>Step 1: Personal Info</h2>
          <input {...form.register('firstName')} placeholder="First Name" />
          <input {...form.register('lastName')} placeholder="Last Name" />
          <input {...form.register('email')} placeholder="Email" />
        </div>
      )}

      {currentStep === 2 && (
        <div>
          <h2>Step 2: Address</h2>
          <input {...form.register('street')} placeholder="Street" />
          <input {...form.register('city')} placeholder="City" />
          <input {...form.register('zipCode')} placeholder="ZIP Code" />
        </div>
      )}

      {currentStep === 3 && (
        <div>
          <h2>Step 3: Payment</h2>
          <input {...form.register('cardNumber')} placeholder="Card Number" />
          <input {...form.register('expiryDate')} placeholder="MM/YY" />
        </div>
      )}

      <div>
        {currentStep > 1 && (
          <button type="button" onClick={handlePrev}>
            Previous
          </button>
        )}
        {currentStep < 3 && (
          <button type="button" onClick={handleNext} disabled={!canProceed}>
            Next
          </button>
        )}
        {currentStep === 3 && (
          <button type="submit">
            Submit
          </button>
        )}
      </div>
    </form>
  );
}

Integration with Form Validation

React Hook Form errors are automatically converted to Manifesto ValidationResult:

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email('Invalid email format'),
  age: z.number().min(18, 'Must be 18 or older')
});

function ValidatedForm() {
  const form = useForm({
    resolver: zodResolver(schema)
  });

  const runtime = useMemo(() => createRuntime(domain), []);
  const bridge = useManifestoBridge(form, runtime);

  // RHF validation errors are available through the adapter
  // bridge.adapter.getValidity('data.email')
  // Returns: { valid: false, issues: [{ code: 'email', message: 'Invalid email format', ... }] }
}

Re-exports

This package re-exports commonly used items from @manifesto-ai/bridge:

export { createBridge, setValue, setMany, executeAction } from '@manifesto-ai/bridge';
export type { Adapter, Actuator, Bridge, Command, BridgeError } from '@manifesto-ai/bridge';

Related Packages

License

MIT