Should I Use useFlow?
useFlow is purpose-built for multi-step UI flows with built-in persistence and analytics. Use this guide to determine if it’s the right fit for your project.
Perfect for useflow
Section titled “Perfect for useflow”Multi-step processes
Section titled “Multi-step processes”If you’re building flows with 2+ steps where users progress through sequential or branching paths:
- Onboarding flows - Welcome → Profile → Preferences → Complete
- Checkout processes - Cart → Shipping → Payment → Confirmation
- Application forms - Personal Info → Employment → Documents → Review
- Wizards - Setup → Configuration → Testing → Deploy
Need persistence
Section titled “Need persistence”If users should continue where they left off after closing browser/app:
- Long forms that users might abandon and return to
- Complex multi-session processes
- Mobile users with interrupted sessions
- Critical flows where lost progress = lost users
Want analytics
Section titled “Want analytics”If you need to track user behavior through your flow:
- Measure conversion at each step
- Identify drop-off points
- A/B test different flow versions
- Monitor user journey completion rates
Type safety matters
Section titled “Type safety matters”If you want compile-time safety for your flow logic:
- Catch navigation errors before runtime
- Autocomplete for step names and context
- Refactor flows with confidence
- Large teams need consistent flow patterns
Consider alternatives
Section titled “Consider alternatives”Single forms
Section titled “Single forms”For one-page forms without steps:
// ❌ Overkill: Simple login form<Flow flow={loginFlow}> {({ renderStep }) => renderStep({ login: <LoginForm /> })}</Flow>
// ✅ Better: Just use a form library<form onSubmit={handleLogin}> <input name="email" /> <input name="password" /> <button>Login</button></form>Use instead: React Hook Form, Formik, or vanilla forms
Complex state machines
Section titled “Complex state machines”For intricate business logic beyond UI flows:
// ❌ Wrong tool: Complex state machineconst [state, send] = useActor(complexBusinessMachine);
// ✅ Better: Use XState for complex logicconst [state, send] = useMachine(orderProcessingMachine);Use instead: XState for complex state machines
Simple page routing
Section titled “Simple page routing”For basic page navigation:
// ❌ Overkill: Simple page routing<Flow flow={pageFlow}> {({ renderStep }) => renderStep({ home: <HomePage />, about: <AboutPage /> })}</Flow>
// ✅ Better: Use a router<Routes> <Route path="/" element={<HomePage />} /> <Route path="/about" element={<AboutPage />} /></Routes>Use instead: Next.js Router, React Router, or similar
Decision framework
Section titled “Decision framework”Ask yourself these questions:
1. How many steps?
Section titled “1. How many steps?”- 1 step: Use a form library
- 2+ steps: ✅ Consider useFlow
2. Do users need to save progress?
Section titled “2. Do users need to save progress?”- Not right now: That’s fine! Adding persistence later is one line of code
- Yes, definitely: ✅ Perfect for useFlow
The reality: Most multi-step flows eventually need persistence. Users will close the tab, their phone will die, they will expect to continue where they left off. With useFlow, it’s trivial to add when (not if) you need it.
3. Do you need step analytics?
Section titled “3. Do you need step analytics?”- Not tracking yet: No problem! Analytics hooks are built-in when you’re ready
- Need conversion data: ✅ Great fit for useFlow
The reality: You’ll want to know where users drop off. Even if you don’t need analytics today, when product asks “where are users getting stuck?”, you’ll be glad it’s just adding an onNext callback.
4. How complex is the logic?
Section titled “4. How complex is the logic?”- Simple linear flow: ✅ Perfect for useFlow
- Some branching: ✅ Great for useFlow
- Complex state machine: Consider XState
5. What’s your team like?
Section titled “5. What’s your team like?”- Small team, simple needs: Any solution works
- Large team, needs consistency: ✅ useFlow provides structure
Real-world examples
Section titled “Real-world examples”Great useFlow use cases
Section titled “Great useFlow use cases”E-commerce Checkout
const checkoutFlow = defineFlow({ id: "checkout", start: "cart", steps: { cart: { next: "shipping" }, shipping: { next: "payment" }, payment: { next: "confirm" }, confirm: {}, },});Benefits:
- ✅ Users don’t lose cart on page refresh
- ✅ Track exactly where users drop off (via analytics hooks)
- ✅ Type-safe step navigation
- ✅ Easy to add A/B testing when needed
User Onboarding
const onboardingFlow = defineFlow({ id: "onboarding", start: "welcome", steps: { welcome: { next: "profile" }, profile: { next: "preferences" }, preferences: { next: "complete" }, complete: {}, },});Benefits:
- ✅ New users can complete onboarding across sessions
- ✅ Track which steps cause drop-offs
- ✅ Skip steps for returning users
- ✅ Measure onboarding completion rates
Poor useFlow use cases
Section titled “Poor useFlow use cases”Contact Form
// ❌ Overkill for single form<Flow flow={contactFlow}> {({ renderStep }) => renderStep({ contact: <ContactForm /> })}</Flow>
// ✅ Much simpler<ContactForm />Page Navigation
// ❌ Wrong tool for routing<Flow flow={navigationFlow}> {({ renderStep }) => renderStep({ home: <HomePage />, about: <AboutPage />, contact: <ContactPage /> })}</Flow>
// ✅ Use proper routing<Router> <Route path="/" component={HomePage} /> <Route path="/about" component={AboutPage} /> <Route path="/contact" component={ContactPage} /></Router>Bundle size considerations
Section titled “Bundle size considerations”useFlow is lightweight - under 5KB gzipped with zero dependencies.
The real comparison: Building equivalent functionality yourself (navigation, persistence, type safety, analytics hooks) would likely result in more code than useFlow itself. You’re trading a small, tested library for maintaining your own implementation.
Need help deciding?
Section titled “Need help deciding?”Still unsure? Here are some quick questions:
Can I use useFlow with my existing form library?
Yes! useFlow complements form libraries perfectly:
function ProfileStep() { const { context, setContext, next } = useFlowState();
// Use your favorite form library for validation const form = useForm({ defaultValues: context, onSubmit: (values) => { setContext(values); // Save to flow context next(); // Navigate to next step }, });
return <form onSubmit={form.handleSubmit}>{/* ... */}</form>;}How do I migrate from my existing solution?
Start incrementally:
- Identify one multi-step flow in your app (pick the simplest one first)
- Rewrite it with useFlow - should be faster and cleaner than original
- Test thoroughly to ensure identical user experience
- Deploy when confident, then migrate other flows
- Remove old code as you replace each flow
No need to rewrite everything at once! Pick flows one by one and migrate as your team has capacity.
What if I only need 2 steps?
2 steps is perfect for useFlow! You still get:
- ✅ Automatic progress persistence
- ✅ Navigation management
- ✅ Analytics hooks
- ✅ Type safety
Even “simple” 2-step flows become complex when you add validation, back navigation, and persistence.
Is the learning curve worth it?
useFlow is extremely simple:
- 5 minutes: Read the Quick Start and understand the concepts
- 10 minutes: Build your first working flow
- 2 minutes: Add persistence (one line of code)
- 2 minutes: Add analytics hooks (one callback)
That’s it. There’s not much to learn - it’s just declarative flow definitions with a few simple hooks.
Next steps
Section titled “Next steps”Ready to get started?
- Quick Start - Build your first flow in 5 minutes
- Examples - See real-world implementations
- Core Concepts - Understand the fundamentals
Still evaluating?
- Why useFlow? - Detailed comparison with alternatives
- Type Safety Guide - See the type safety benefits
- Troubleshooting - Common questions answered