Performance Guide
Learn how to optimize Hero Hook Form for better performance and user experience.
Memoization
Component Memoization
All field components are automatically memoized with React.memo:
// âś… Already optimized - no additional work needed
<InputField name="email" label="Email" />
<CheckboxField name="newsletter" label="Subscribe" />Custom Field Components
Memoize custom field components:
import React, { memo } from "react";
import { InputField } from "@rachelallyson/hero-hook-form";
const CustomField = memo(({ name, label, ...props }) => {
return (
<div className="custom-field">
<InputField name={name} label={label} {...props} />
<div className="custom-addition">Additional content</div>
</div>
);
});
// Use in form
<CustomField name="customField" label="Custom Field" />Field Configuration Memoization
Memoize field configurations to prevent re-creation:
import { useMemo } from "react";
import { FormFieldHelpers } from "@rachelallyson/hero-hook-form";
function MyForm() {
// Memoize field configurations
const fields = useMemo(() => [
FormFieldHelpers.input("name", "Name"),
FormFieldHelpers.input("email", "Email", { type: "email" }),
FormFieldHelpers.checkbox("newsletter", "Subscribe to newsletter"),
], []);
return (
<ZodForm
config={{
schema: mySchema,
fields,
onSubmit: handleSubmit,
}}
/>
);
}Debounced Validation
Basic Debouncing
Use useDebouncedValidation for expensive validation:
import { useDebouncedValidation } from "@rachelallyson/hero-hook-form";
function ExpensiveValidationField() {
const [value, setValue] = useState("");
const { debouncedValue, isDebouncing } = useDebouncedValidation(value, 300);
const handleValidation = async (val: string) => {
// Expensive validation (API call, complex calculation)
const response = await fetch(`/api/validate?value=${val}`);
return response.ok;
};
return (
<InputField
name="expensiveField"
label="Field with Expensive Validation"
rules={{
validate: async (val) => {
if (val !== debouncedValue) return true; // Skip if not debounced value
return await handleValidation(val);
},
}}
/>
);
}Custom Debounce Configuration
Configure debounce behavior:
const { debouncedValue, isDebouncing, cancel } = useDebouncedValidation(
fieldValue,
500, // 500ms delay
{
leading: false, // Don't call on leading edge
trailing: true, // Call on trailing edge
maxWait: 2000, // Maximum wait time
}
);Visual Debounce Feedback
Show debounce status to users:
function DebouncedField() {
const [value, setValue] = useState("");
const { debouncedValue, isDebouncing } = useDebouncedValidation(value, 300);
return (
<div>
<InputField
name="debouncedField"
label="Debounced Field"
value={value}
onChange={setValue}
/>
{isDebouncing && (
<div className="debounce-indicator">
<Spinner size="sm" />
<span>Validating...</span>
</div>
)}
</div>
);
}Field Array Optimization
Efficient Field Array Rendering
Optimize field arrays for better performance:
import { FieldArrayField } from "@rachelallyson/hero-hook-form";
import { memo } from "react";
const ItemField = memo(({ index, item }: { index: number; item: any }) => (
<div className="item-field">
<InputField name={`items.${index}.name`} label="Item Name" />
<InputField name={`items.${index}.value`} label="Item Value" />
</div>
));
function OptimizedFieldArray() {
return (
<FieldArrayField
name="items"
label="Items"
renderItem={(item, index) => (
<ItemField key={index} index={index} item={item} />
)}
addButtonText="Add Item"
removeButtonText="Remove"
/>
);
}Virtual Scrolling for Large Arrays
For very large field arrays, implement virtual scrolling:
import { FixedSizeList as List } from "react-window";
function VirtualizedFieldArray() {
const [items, setItems] = useState([]);
const Row = ({ index, style }: { index: number; style: any }) => (
<div style={style}>
<InputField name={`items.${index}.name`} label="Item Name" />
</div>
);
return (
<List
height={400}
itemCount={items.length}
itemSize={50}
>
{Row}
</List>
);
}Conditional Field Optimization
Efficient Conditional Rendering
Optimize conditional fields to prevent unnecessary re-renders:
import { ConditionalField } from "@rachelallyson/hero-hook-form";
import { memo, useMemo } from "react";
const ConditionalFieldComponent = memo(({
condition,
render,
values
}: {
condition: (values: any) => boolean;
render: () => React.ReactNode;
values: any;
}) => {
const shouldRender = useMemo(() => condition(values), [condition, values]);
if (!shouldRender) return null;
return <>{render()}</>;
});
// Use in form
<ConditionalField
name="conditionalField"
condition={(values) => values.showField === true}
render={() => (
<InputField name="conditionalField" label="Conditional Field" />
)}
/>Condition Memoization
Memoize condition functions:
const conditionFunctions = useMemo(() => ({
showAdvanced: (values: any) => values.userType === 'advanced',
showBusiness: (values: any) => values.accountType === 'business',
}), []);
<ConditionalField
name="advancedField"
condition={conditionFunctions.showAdvanced}
render={() => <AdvancedFields />}
/>Performance Monitoring
Built-in Performance Monitoring
Use the built-in performance monitoring:
import { usePerformanceMonitor } from "@rachelallyson/hero-hook-form";
function MonitoredForm() {
const {
renderTime,
validationTime,
submissionTime,
isMonitoring,
} = usePerformanceMonitor();
return (
<div>
<ZodForm config={{ /* ... */ }} />
{isMonitoring && (
<div className="performance-info">
<p>Render: {renderTime}ms</p>
<p>Validation: {validationTime}ms</p>
<p>Submission: {submissionTime}ms</p>
</div>
)}
</div>
);
}Custom Performance Tracking
Implement custom performance tracking:
import { useEffect, useRef } from "react";
function PerformanceTracker() {
const renderStart = useRef<number>();
const validationStart = useRef<number>();
useEffect(() => {
renderStart.current = performance.now();
return () => {
const renderTime = performance.now() - (renderStart.current || 0);
console.log(`Form render time: ${renderTime}ms`);
};
}, []);
const trackValidation = () => {
validationStart.current = performance.now();
};
const trackValidationEnd = () => {
const validationTime = performance.now() - (validationStart.current || 0);
console.log(`Validation time: ${validationTime}ms`);
};
return (
<ZodForm
config={{
fields: myFields,
onSubmit: handleSubmit,
onValidationStart: trackValidation,
onValidationEnd: trackValidationEnd,
}}
/>
);
}Bundle Size Optimization
Tree Shaking
Ensure proper tree shaking:
// âś… Good: Import only what you need
import { ZodForm, FormFieldHelpers } from "@rachelallyson/hero-hook-form";
// ❌ Bad: Import entire library
import * as HeroHookForm from "@rachelallyson/hero-hook-form";Dynamic Imports
Use dynamic imports for large features:
import { lazy, Suspense } from "react";
const AdvancedForm = lazy(() => import('./AdvancedForm'));
function MyApp() {
return (
<Suspense fallback={<div>Loading...</div>}>
<AdvancedForm />
</Suspense>
);
}Code Splitting
Split forms by route or feature:
// routes/ContactForm.tsx
export default function ContactForm() {
return <ZodForm config={contactConfig} />;
}
// routes/SettingsForm.tsx
export default function SettingsForm() {
return <ZodForm config={settingsConfig} />;
}
// App.tsx
import { lazy } from "react";
const ContactForm = lazy(() => import('./routes/ContactForm'));
const SettingsForm = lazy(() => import('./routes/SettingsForm'));Memory Optimization
Cleanup Event Listeners
Properly cleanup event listeners:
import { useEffect, useRef } from "react";
function FormWithCleanup() {
const cleanupRef = useRef<(() => void)[]>([]);
useEffect(() => {
const handleResize = () => {
// Handle resize
};
window.addEventListener('resize', handleResize);
cleanupRef.current.push(() => {
window.removeEventListener('resize', handleResize);
});
return () => {
cleanupRef.current.forEach(cleanup => cleanup());
};
}, []);
return <ZodForm config={{ /* ... */ }} />;
}Avoid Memory Leaks
Prevent memory leaks in form components:
import { useEffect, useRef } from "react";
function FormWithRefs() {
const formRef = useRef<HTMLFormElement>(null);
const timeoutRef = useRef<NodeJS.Timeout>();
useEffect(() => {
// Set up timeout
timeoutRef.current = setTimeout(() => {
// Do something
}, 1000);
return () => {
// Cleanup timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
return <form ref={formRef}><ZodForm config={{ /* ... */ }} /></form>;
}Server-Side Rendering (SSR)
SSR Compatibility
Ensure forms work with SSR:
import { useEffect, useState } from "react";
function SSRCompatibleForm() {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
if (!isClient) {
return <div>Loading form...</div>;
}
return <ZodForm config={{ /* ... */ }} />;
}Hydration Optimization
Optimize hydration performance:
function OptimizedSSRForm() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return (
<div className="form-skeleton">
{/* Skeleton UI that matches form structure */}
</div>
);
}
return <ZodForm config={{ /* ... */ }} />;
}Performance Testing
Load Testing
Test form performance under load:
// Performance test
describe('Form Performance', () => {
it('should render within performance budget', () => {
const start = performance.now();
render(<MyForm />);
const end = performance.now();
expect(end - start).toBeLessThan(100); // 100ms budget
});
it('should handle large field arrays efficiently', () => {
const largeFieldArray = Array.from({ length: 1000 }, (_, i) => ({
name: `item${i}`,
value: `value${i}`,
}));
const start = performance.now();
render(<FieldArrayForm items={largeFieldArray} />);
const end = performance.now();
expect(end - start).toBeLessThan(500); // 500ms budget
});
});Memory Usage Testing
Monitor memory usage:
// Memory usage test
describe('Memory Usage', () => {
it('should not leak memory', () => {
const initialMemory = performance.memory?.usedJSHeapSize || 0;
// Render and unmount form multiple times
for (let i = 0; i < 100; i++) {
const { unmount } = render(<MyForm />);
unmount();
}
const finalMemory = performance.memory?.usedJSHeapSize || 0;
const memoryIncrease = finalMemory - initialMemory;
expect(memoryIncrease).toBeLessThan(1000000); // 1MB limit
});
});Performance Best Practices
1. Minimize Re-renders
// âś… Good: Memoized components
const MemoizedField = memo(InputField);
// ❌ Bad: Unnecessary re-renders
function BadForm() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<InputField name="email" label="Email" /> {/* Re-renders on count change */}
</div>
);
}2. Optimize Validation
// âś… Good: Debounced validation
const { debouncedValue } = useDebouncedValidation(value, 300);
// ❌ Bad: Validation on every keystroke
<InputField
name="email"
rules={{
validate: async (value) => {
// Expensive API call on every keystroke
return await validateEmail(value);
},
}}
/>3. Use Efficient Data Structures
// âś… Good: Efficient field array
const fields = useMemo(() => [
FormFieldHelpers.input("name", "Name"),
FormFieldHelpers.input("email", "Email"),
], []);
// ❌ Bad: Recreated on every render
function BadForm() {
const fields = [
FormFieldHelpers.input("name", "Name"),
FormFieldHelpers.input("email", "Email"),
];
return <ZodForm config={{ fields, onSubmit }} />;
}4. Monitor Performance
// âś… Good: Performance monitoring
const { renderTime } = usePerformanceMonitor();
if (renderTime > 100) {
console.warn('Form render time exceeds budget:', renderTime);
}Performance Checklist
âś… Optimization
- Components are memoized
- Field configurations are memoized
- Expensive validation is debounced
- Field arrays are optimized
- Conditional fields are efficient
âś… Monitoring
- Performance is monitored
- Memory usage is tracked
- Bundle size is optimized
- SSR compatibility is ensured
âś… Testing
- Performance tests are written
- Load testing is performed
- Memory leak tests pass
- Performance budgets are met
Tools and Resources
- React DevTools Profiler: Profile component performance
- Chrome DevTools: Monitor memory usage and performance
- Bundle Analyzer: Analyze bundle size
- Lighthouse: Audit performance and accessibility
- Web Vitals: Monitor Core Web Vitals