Have you ever needed a form where changing one field updates the options of another? Like selecting a category and seeing subcategories automatically update?
Most developers reach for useEffect + manual state management. But there's a better way.
The Problem
Let's say you have two autocomplete fields:
- Category - static options
- Subcategory - depends on category, loads dynamically
The traditional approach requires:
- Tracking category changes
- Triggering updates when category changes
- Manual state management for loading/error states
- Clearing subcategory when category changes
- Managing async operations
It gets complex fast when you add more dependencies.
The DashForge Solution
DashForge uses reactions - a declarative way to handle field dependencies:
import { useState } from "react";
import { DashForm } from "@dashforge/forms";
import { Autocomplete } from "@dashforge/ui";
import { Box, Button, Card, Stack, Typography } from "@mui/material";
interface FormValues {
category: string | null;
subcategory: string | null;
}
const categories = ["Electronics", "Clothing", "Books"];
const subcategoriesByCategory: Record = {
Electronics: ["Phones", "Laptops", "Tablets", "Accessories"],
Clothing: ["Shirt", "Pants", "Shoes", "Dresses"],
Books: ["Fiction", "Non-Fiction", "Science", "History"],
};
async function fetchSubcategories(category: string | null) {
await new Promise((resolve) => setTimeout(resolve, 500));
return category ? subcategoriesByCategory[category] || [] : [];
}
export function DependencyForm() {
const [formData, setFormData] = useState(null);
const onSubmit = (data: FormValues) => {
setFormData(data);
};
return (
<DashForm
onSubmit={onSubmit}
defaultValues={{ category: null, subcategory: null }}
reactions={[
{
id: "update-subcategory-options",
watch: ["category"],
run: async ({ getValue, setRuntime }) => {
const category = getValue("category");
setRuntime("subcategory", {
status: "loading",
error: null,
data: { options: [] },
});
const subcategories = await fetchSubcategories(category);
setRuntime("subcategory", {
status: "ready",
error: null,
data: { options: subcategories },
});
},
},
]}
>
Submit
{formData && (
{JSON.stringify(formData, null, 2)}
)}
);
}
How It Works
The key is the reactions array:
- watch - Specify which fields to monitor
- run - When watched fields change, execute this function
- getValue - Read current field values from the form
- setRuntime - Update field data at runtime (options, loading state, errors)
When category changes:
- Reaction triggers automatically
- Fetch new subcategories
- Update subcategory field with new options
- User sees updated options immediately
Why This Approach is Better
No Manual Side Effects
The form handles dependencies automatically. No need for useEffect or manual state management.
Clean Dependencies
Just declare what you're watching with the watch array. No complex dependency tracking.
Loading States Built-in
Set status: "loading" and the field knows it's fetching. Built-in support for async operations.
Type-Safe
Full TypeScript support for getValue and setRuntime.
Scales Well
Add another dependent field? Add another reaction. Complexity stays linear, not exponential.
Advanced: Multiple Dependencies
What if subcategory affects another field? Just add another reaction:
reactions={[
{
id: "update-subcategory-options",
watch: ["category"],
run: async ({ getValue, setRuntime }) => {
// Update subcategory based on category
},
},
{
id: "update-product-options",
watch: ["subcategory"],
run: async ({ getValue, setRuntime }) => {
// Update product based on subcategory
},
},
]}
The form orchestrates the entire dependency chain.
Real-World Scenarios
This pattern works for:
- Cascading dropdowns - Country → State → City
- Dynamic pricing - Product → Variant → Price
- Conditional visibility - Type → Related options
- Async data loading - Search field → fetch results
- Complex workflows - Multi-step forms with dependencies
Getting Started
Install DashForge:
npm install @dashforge/forms @dashforge/ui
See the full documentation for API details.
Check out the starter kits for production examples.
The Point
Dependent form fields don't need to be complex. With the right abstraction, they become straightforward: declare what you're watching, declare what updates, let the form handle the rest
Top comments (0)