React form hook with built-in backend submission. No server code needed. Works with shadcn/ui, MUI, and any component library. Built on react-hook-form.
npm install @formflow.sh/reactProduction-grade React form hook with built-in backend submission. No server code needed.
``bash`
npm install @formflow.sh/reactor
pnpm add @formflow.sh/reactor
yarn add @formflow.sh/react
`tsx
import { useFormFlow } from '@formflow.sh/react';
import { Input } from '@/components/ui/input'; // any UI library
import { Button } from '@/components/ui/button';
export function ContactForm() {
const { register, handleSubmit, formState } = useFormFlow({
apiKey: process.env.NEXT_PUBLIC_FORMFLOW_API_KEY,
onSuccess: () => alert('Thanks for your message!'),
});
return (
That's it! Your form submits to FormFlow's backend automatically. No API routes, no server code.
Why useFormFlow?
- Works with ANY UI library: shadcn/ui, Material-UI, Chakra UI, or native HTML
- Built on react-hook-form: Battle-tested validation and state management (41k stars)
- Backend included: Form submission, storage, and email notifications handled automatically
- TypeScript-first: Excellent IntelliSense and type safety
- Production-ready: Comprehensive test coverage, industry-standard patterns
API Reference
$3
React hook for form state management and backend submission.
#### Options
All options from react-hook-form are supported, plus:
-
apiKey (string, required): FormFlow API key from dashboard
`tsx
apiKey: 'pk_live_abc123'
`-
onSuccess (function, optional): Called after successful submission
`tsx
onSuccess: (data, response) => {
console.log('Submitted:', data);
console.log('Submission ID:', response.submissionId);
}
`-
onError (function, optional): Called when submission fails
`tsx
onError: (error) => {
console.error('Error:', error.message);
}
`-
defaultValues (object, optional): Initial form values
`tsx
defaultValues: {
email: 'user@example.com',
newsletter: true
}
`#### Returns
All methods from react-hook-form are available, including:
-
register(name, options): Register form field
`tsx
`-
handleSubmit: Form submission handler (enhanced to submit to FormFlow)
`tsx
`-
formState: Current form state
`tsx
formState.isSubmitting // true while submitting
formState.errors // validation errors
formState.isValid // form validity
`-
setValue(name, value): Programmatically set field value
`tsx
setValue('email', 'new@example.com')
`-
getValues(): Get all current values
`tsx
const values = getValues()
`-
reset(): Reset form to defaults
`tsx
reset()
`-
watch(name): Watch field value for conditional logic
`tsx
const newsletter = watch('newsletter')
`Works With Any UI Library
$3
`tsx
import { useFormFlow } from '@formflow.sh/react';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';const { register, handleSubmit, formState } = useFormFlow({
apiKey: process.env.NEXT_PUBLIC_FORMFLOW_API_KEY
});
`$3
`tsx
import { useFormFlow } from '@formflow.sh/react';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';const { register, handleSubmit } = useFormFlow({
apiKey: process.env.NEXT_PUBLIC_FORMFLOW_API_KEY
});
`$3
`tsx
import { useFormFlow } from '@formflow.sh/react';
import { Input, Button } from '@chakra-ui/react';const { register, handleSubmit } = useFormFlow({
apiKey: process.env.NEXT_PUBLIC_FORMFLOW_API_KEY
});
`$3
`tsx
import { useFormFlow } from '@formflow.sh/react';const { register, handleSubmit, formState } = useFormFlow({
apiKey: process.env.NEXT_PUBLIC_FORMFLOW_API_KEY
});
`Form Validation
FormFlow uses react-hook-form's validation, which is both powerful and flexible.
$3
`tsx
{...register('email', {
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Invalid email address'
}
})}
type="email"
/>
{formState.errors.email && (
{formState.errors.email.message}
)}
`$3
`tsx
{...register('password', {
required: 'Password is required',
minLength: {
value: 8,
message: 'Password must be at least 8 characters'
},
validate: {
hasUpperCase: (value) =>
/[A-Z]/.test(value) || 'Password must contain an uppercase letter',
hasLowerCase: (value) =>
/[a-z]/.test(value) || 'Password must contain a lowercase letter',
hasNumber: (value) =>
/\d/.test(value) || 'Password must contain a number'
}
})}
type="password"
/>
`$3
-
required: Field is required
- pattern: Regex pattern matching
- minLength / maxLength: String length validation
- min / max: Number range validation
- validate: Custom validation functionsSee react-hook-form validation docs for all options.
Examples
/examples directory for complete working examples:$3
Simplest possible implementation using native HTML elements.
Features: Native HTML inputs, basic error handling, loading states
When to use: Quick prototypes, simple forms, learning the basics
$3
Shows integration with shadcn/ui component library.
Features: shadcn/ui Input, Button, Label, Textarea components
When to use: Production apps using shadcn/ui, modern design systems
$3
Comprehensive validation patterns using react-hook-form.
Features: Required fields, email patterns, min/max length, number ranges, custom validation
When to use: Forms requiring data validation, user input safety
$3
Advanced patterns for production applications.
Features: Default values, error handling, controlled components with
watch(), conditional fields, form resetWhen to use: Complex forms, production applications, advanced requirements
Live Examples
Visit formflow.sh/docs/examples for interactive demos you can test in your browser.
Common Patterns
$3
`tsx
const { register, handleSubmit, formState } = useFormFlow({
apiKey: process.env.NEXT_PUBLIC_FORMFLOW_API_KEY,
onSuccess: () => alert('Subscribed!'),
});return (
);
`$3
`tsx
import { toast } from 'sonner'; // or your toast libraryconst { register, handleSubmit } = useFormFlow({
apiKey: process.env.NEXT_PUBLIC_FORMFLOW_API_KEY,
onSuccess: () => toast.success('Message sent!'),
onError: () => toast.error('Failed to send message'),
});
`$3
`tsx
const [step, setStep] = useState(1);
const { register, handleSubmit, trigger } = useFormFlow({
apiKey: process.env.NEXT_PUBLIC_FORMFLOW_API_KEY,
});const handleNext = async () => {
const isValid = await trigger(['email', 'name']); // Validate current step
if (isValid) setStep(2);
};
`Error Handling
FormFlow provides detailed error information for different failure scenarios:
`tsx
const { register, handleSubmit } = useFormFlow({
apiKey: process.env.NEXT_PUBLIC_FORMFLOW_API_KEY,
onError: (error) => {
if (error.message?.includes('401')) {
alert('Invalid API key');
} else if (error.message?.includes('402')) {
alert('Submission quota exceeded. Please upgrade your plan.');
} else if (error.message?.includes('429')) {
alert('Too many requests. Please try again in a few minutes.');
} else if (error.message?.includes('Network')) {
alert('Network error. Please check your internet connection.');
} else {
alert('An unexpected error occurred. Please try again.');
}
},
});
`Development Mode
All forms work without an API key during development. Submissions are logged to the console:
`tsx
const { register, handleSubmit } = useFormFlow({
apiKey: process.env.NEXT_PUBLIC_FORMFLOW_API_KEY, // undefined in dev = console logs
});
`To test with a real backend:
1. Sign up at formflow.sh
2. Get your API key from the dashboard
3. Add it to your
.env file:
`env
NEXT_PUBLIC_FORMFLOW_API_KEY=pk_live_xxx
`Environment Variables
$3
`env
NEXT_PUBLIC_FORMFLOW_API_KEY=your_api_key_here
`$3
`env
VITE_FORMFLOW_API_KEY=your_api_key_here
`Update your code:
`tsx
apiKey: import.meta.env.VITE_FORMFLOW_API_KEY
`$3
`env
REACT_APP_FORMFLOW_API_KEY=your_api_key_here
`Update your code:
`tsx
apiKey: process.env.REACT_APP_FORMFLOW_API_KEY
`TypeScript
Fully typed with excellent IntelliSense support:
`typescript
import type {
UseFormFlowOptions,
UseFormFlowReturn,
FormFlowSubmitResponse,
FormFlowError
} from '@formflow.sh/react';// Type your form data
interface ContactFormData {
email: string;
name: string;
message: string;
}
const { register, handleSubmit } = useFormFlow({
apiKey: process.env.NEXT_PUBLIC_FORMFLOW_API_KEY,
onSuccess: (data, response) => {
// data is typed as ContactFormData
console.log(data.email);
console.log(response.submissionId);
},
});
`Migration from Wrapper Components
If you're using the old wrapper-based components (
, ), they still work but are deprecated. Migrate to the hook-based API:Before:
`tsx
import { ContactForm } from '@formflow.sh/react';
`After:
`tsx
import { useFormFlow } from '@formflow.sh/react';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';export function ContactForm() {
const { register, handleSubmit, formState } = useFormFlow({
apiKey: process.env.NEXT_PUBLIC_FORMFLOW_API_KEY,
});
return (
);
}
`Benefits:
- Works with your existing component library
- More flexible and customizable
- Industry-standard pattern
- Better TypeScript support
Troubleshooting
$3
Make sure you've installed the package:
`bash
npm install @formflow.sh/react
`$3
Check that your API key is correctly set in your
.env file and starts with pk_live_ or pk_test_.$3
1. Check browser console for errors
2. Verify
handleSubmit is passed to onSubmit prop
3. Ensure form has a submit button with type="submit"
4. Confirm API key is set (or remove it for dev mode)$3
Ensure you have the latest version:
`bash
npm install @formflow.sh/react@latest
`$3
react-hook-form validation is client-side only. Make sure to:
1. Pass validation options to
register()
2. Display errors from formState.errors
3. Check formState.isValid before allowing submissionContributing
Found a useful pattern? Submit a PR with a new example in the
/examples` directory!- Full Documentation
- Live Examples
- API Reference
- react-hook-form Docs
- TypeScript Types
- Free: 50 submissions/month
- Maker: $9/month - 1,000 submissions
- Pro: $29/month - 10,000 submissions
- Business: $99/month - 100,000 submissions
See formflow.sh/pricing for details.
MIT