High performance subscription-based form state management for React
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
FormSpy component for observing form state changes without rendering form fields, perfect for external components that need form state updates.
Component that subscribes to form state changes and renders UI based on form state without rendering actual form fields.
/**
* Component for observing form state without rendering form fields
* @param props - FormSpy configuration and render props
* @returns React element or null
*/
const FormSpy: <FormValues = Record<string, any>>(
props: FormSpyProps<FormValues>
) => React.ReactElement;
interface FormSpyProps<FormValues = Record<string, any>>
extends UseFormStateParams<FormValues>,
RenderableProps<FormSpyRenderProps<FormValues>> {}
interface FormSpyRenderProps<FormValues = Record<string, any>>
extends FormState<FormValues> {
/** Form API instance for programmatic control */
form: FormApi<FormValues>;
}
interface UseFormStateParams<FormValues = Record<string, any>> {
/** Callback when form state changes */
onChange?: (formState: FormState<FormValues>) => void;
/** Form state subscription configuration */
subscription?: FormSubscription;
}Usage Examples:
import React from "react";
import { Form, Field, FormSpy } from "react-final-form";
// Basic FormSpy usage
function BasicFormSpy() {
return (
<Form onSubmit={(values) => console.log(values)}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="firstName" component="input" />
<FormSpy>
{({ values, dirty, invalid }) => (
<div>
<p>Form is {dirty ? "dirty" : "pristine"}</p>
<p>Form is {invalid ? "invalid" : "valid"}</p>
<pre>{JSON.stringify(values, null, 2)}</pre>
</div>
)}
</FormSpy>
</form>
)}
</Form>
);
}
// FormSpy with subscription
function OptimizedFormSpy() {
return (
<Form onSubmit={(values) => console.log(values)}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="email" component="input" type="email" />
<FormSpy subscription={{ values: true, submitting: true }}>
{({ values, submitting }) => (
<div>
{submitting && <p>Submitting...</p>}
<p>Email: {values.email}</p>
</div>
)}
</FormSpy>
</form>
)}
</Form>
);
}
// FormSpy with onChange callback
function CallbackFormSpy() {
const handleFormChange = (state: any) => {
console.log("Form state changed:", state);
// Save to localStorage, send analytics, etc.
};
return (
<Form onSubmit={(values) => console.log(values)}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="username" component="input" />
<FormSpy onChange={handleFormChange} />
</form>
)}
</Form>
);
}FormSpy provides access to the form API for programmatic form control.
/**
* FormSpy render props include form API for programmatic control
*/
interface FormSpyRenderProps<FormValues = Record<string, any>>
extends FormState<FormValues> {
/** Complete form API instance */
form: FormApi<FormValues>;
}Usage Examples:
// FormSpy with form control buttons
function FormControlSpy() {
return (
<Form onSubmit={(values) => console.log(values)}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="message" component="textarea" />
<FormSpy>
{({ form, pristine, invalid, values }) => (
<div>
<button
type="button"
onClick={() => form.reset()}
disabled={pristine}
>
Reset Form
</button>
<button
type="button"
onClick={() => form.change("message", "Hello World")}
>
Set Default Message
</button>
<button
type="button"
onClick={() => form.focus("message")}
>
Focus Message
</button>
<pre>{JSON.stringify(values, null, 2)}</pre>
</div>
)}
</FormSpy>
</form>
)}
</Form>
);
}
// FormSpy for conditional rendering
function ConditionalFormSpy() {
return (
<Form onSubmit={(values) => console.log(values)}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="accountType">
{({ input }) => (
<select {...input}>
<option value="">Select Account Type</option>
<option value="personal">Personal</option>
<option value="business">Business</option>
</select>
)}
</Field>
<FormSpy subscription={{ values: true }}>
{({ values }) => (
<>
{values.accountType === "business" && (
<Field name="companyName" component="input" placeholder="Company Name" />
)}
{values.accountType === "personal" && (
<Field name="dateOfBirth" component="input" type="date" />
)}
</>
)}
</FormSpy>
</form>
)}
</Form>
);
}FormSpy supports the same subscription configuration as Form components for performance optimization.
/**
* FormSpy subscription options for optimized rendering
*/
interface FormSubscription {
active?: boolean;
dirty?: boolean;
dirtyFields?: boolean;
dirtySinceLastSubmit?: boolean;
error?: boolean;
errors?: boolean;
hasSubmitErrors?: boolean;
hasValidationErrors?: boolean;
initialValues?: boolean;
invalid?: boolean;
modified?: boolean;
modifiedSinceLastSubmit?: boolean;
pristine?: boolean;
submitError?: boolean;
submitErrors?: boolean;
submitFailed?: boolean;
submitSucceeded?: boolean;
submitting?: boolean;
touched?: boolean;
valid?: boolean;
validating?: boolean;
values?: boolean;
visited?: boolean;
}Usage Example:
// Optimized FormSpy with minimal subscriptions
function MinimalFormSpy() {
return (
<Form onSubmit={(values) => console.log(values)}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="search" component="input" type="search" />
{/* Only re-renders when values change */}
<FormSpy subscription={{ values: true }}>
{({ values }) => (
<div>
Search results for: {values.search}
</div>
)}
</FormSpy>
{/* Only re-renders when form state changes */}
<FormSpy subscription={{ pristine: true, invalid: true, submitting: true }}>
{({ pristine, invalid, submitting }) => (
<button type="submit" disabled={submitting || pristine || invalid}>
{submitting ? "Searching..." : "Search"}
</button>
)}
</FormSpy>
</form>
)}
</Form>
);
}FormSpy can be used outside of form render functions to monitor form state from parent components.
Usage Example:
// External form state monitoring
function ExternalMonitor() {
const [formState, setFormState] = React.useState<any>(null);
return (
<div>
<div>
External form monitor:
<pre>{JSON.stringify(formState, null, 2)}</pre>
</div>
<Form onSubmit={(values) => console.log(values)}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="data" component="input" />
<FormSpy onChange={setFormState} />
<button type="submit">Submit</button>
</form>
)}
</Form>
</div>
);
}FormSpy is optimized for performance with subscription-based updates and minimal re-rendering.
/**
* FormSpy performance characteristics:
* - Only re-renders when subscribed form state changes
* - Supports granular subscriptions for optimal performance
* - Can be used for onChange callbacks without rendering
* - Minimal memory footprint with automatic cleanup
*/Usage Example:
// Performance-optimized FormSpy usage
function PerformantFormSpy() {
return (
<Form onSubmit={(values) => console.log(values)}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="title" component="input" />
<Field name="content" component="textarea" />
{/* Separate spies for different concerns */}
<FormSpy subscription={{ submitting: true }}>
{({ submitting }) => submitting && <div>Saving...</div>}
</FormSpy>
<FormSpy subscription={{ values: true }}>
{({ values }) => (
<div>Character count: {(values.content || "").length}</div>
)}
</FormSpy>
{/* Silent monitoring without render */}
<FormSpy
onChange={(state) => {
// Auto-save draft every 30 seconds
if (state.dirty) {
console.log("Auto-saving draft...");
}
}}
/>
</form>
)}
</Form>
);
}Install with Tessl CLI
npx tessl i tessl/npm-react-final-form