Atlas Forms
Using State Hooks
Atlas Forms exposes three hooks that give custom player components access to form state, visibility logic, and dependency information. All three hooks must be called inside a component that is a descendant of FormStateProvider.
useAtlasForm
The primary hook. Returns the full form state and the actions used to change it.
import { useAtlasForm } from '@atlas-forms/player-components-react';
interface AtlasFormState {
// Current field values — snapshot, updated on every change
values: Record<string, any>;
// Validation error messages, keyed by fieldId
errors: Record<string, string>;
// True when any value differs from initialValues
isDirty: boolean;
// True when errors is empty after the last validation run
isValid: boolean;
// True while submitForm() is resolving
isSubmitting: boolean;
// Write a single field value and trigger change events
setFieldValue: (fieldId: string, value: any) => void;
// Run validation then call the onSubmit prop; sets isSubmitting during execution
submitForm: () => Promise<void>;
// Direct access to the underlying FormEngine
engine: FormEngine;
}
// Usage
const MyPlayerInner: React.FC = () => {
const {
values,
errors,
isDirty,
isValid,
isSubmitting,
setFieldValue,
submitForm,
engine,
} = useAtlasForm();
// Programmatic field write
const applyDefaults = () => {
setFieldValue('currency', 'GBP');
setFieldValue('vatRate', 0.20);
};
// Custom pre-submit action
const handleAction = async () => {
const errs = await engine.validate();
if (Object.keys(errs).length === 0) {
await submitForm();
} else {
console.warn('Validation failed:', errs);
}
};
return (
<div>
{isDirty && <p className="unsaved-badge">Unsaved changes</p>}
{/* ... */}
</div>
);
};
useFormVisibility
Returns helpers for evaluating per-control visibility rules and mode visibility settings.
import { useFormVisibility } from '@atlas-forms/player-components-react';
interface FormVisibilityState {
// Returns true if the control should be rendered in the current mode and value state
isVisible: (controlId: string) => boolean;
// Returns all controls whose isVisible() is true — re-evaluated when values change
getVisibleControls: () => FormControl[];
}
// Usage
const SectionRenderer: React.FC<{ section: FormSection; controls: FormControl[] }> = ({
section,
controls,
}) => {
const { isVisible } = useFormVisibility();
// Filter once at the section level — do not call isVisible inside each ControlRenderer
const visibleControls = controls.filter(c => isVisible(c.id));
if (visibleControls.length === 0) return null;
return (
<div className="section">
<h3>{section.title}</h3>
{visibleControls.map(c => (
<ControlRenderer key={c.id} control={c} />
))}
</div>
);
};
Call useFormVisibility Once Per Component
Call
useFormVisibility() once at the top of a component and pass isVisible down. Do not call the hook inside a loop or a child component that is rendered many times — each call subscribes to the form context and would generate redundant re-render subscriptions.
useControlDependencies
Returns the dependency graph for a specific control — which controls it reads from, and which controls cascade from it.
import { useControlDependencies } from '@atlas-forms/player-components-react';
interface ControlDependencies {
// IDs of controls whose values this control reads (via visibilityRule or computed expressions)
dependsOn: string[];
// IDs of controls that re-evaluate when this control's value changes
cascadesTo: string[];
}
// Usage — highlight dependent controls in a Studio-style dependency viewer
const DependencyHighlighter: React.FC<{ controlId: string }> = ({ controlId }) => {
const { dependsOn, cascadesTo } = useControlDependencies(controlId);
return (
<div>
{dependsOn.length > 0 && (
<p>Reads values from: {dependsOn.join(', ')}</p>
)}
{cascadesTo.length > 0 && (
<p>Triggers re-evaluation of: {cascadesTo.join(', ')}</p>
)}
</div>
);
};
Hook Rules Summary
| Hook | Must be inside FormStateProvider? | Re-renders when | Typical use |
|---|---|---|---|
useAtlasForm | Yes | Any field value, error, or submit state changes | Submit logic, programmatic field writes, isDirty indicator |
useFormVisibility | Yes | Any value change that affects a visibility rule | Filter control lists before rendering |
useControlDependencies | Yes | Schema changes (rare) | Design-mode dependency visualisation |
Combining All Three Hooks
// A complete custom player inner component using all three hooks
const FullFeaturedInner: React.FC<{ onSubmit: (v: any) => Promise<void> }> = ({ onSubmit }) => {
const {
values, errors, isDirty, isSubmitting, submitForm, engine,
} = useAtlasForm();
const { isVisible, getVisibleControls } = useFormVisibility();
const schema = engine.getSchema();
const sections = schema.sections ?? [];
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await submitForm();
if (Object.keys(errors).length === 0) {
await onSubmit(values);
}
};
return (
<form onSubmit={handleSubmit}>
{isDirty && <div className="unsaved-banner">You have unsaved changes</div>}
{sections.map(section => {
const controls = (schema.controls ?? [])
.filter(c => c.sectionId === section.id && isVisible(c.id));
if (controls.length === 0) return null;
return (
<div key={section.id} className="section-block">
<h3>{section.title}</h3>
{controls.map(c => <ControlRenderer key={c.id} control={c} />)}
</div>
);
})}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
};
getVisibleControls for Progress Tracking
Use
getVisibleControls() to compute a progress percentage — the ratio of visible controls with non-empty values to the total number of visible controls. Invisible controls (hidden by mode or expression) are excluded automatically so your progress bar never counts fields the user cannot see.