form.getFieldProps()
Quick Start (30 seconds)
const form = Forms.create(
{
email: '',
password: ''
},
{
email: (value) => !value.includes('@') ? 'Invalid email' : '',
password: (value) => value.length < 8 ? 'Too short' : ''
}
);
// Get all props for binding in one call
const emailProps = form.getFieldProps('email');
// Spread onto input - complete binding!
emailInput.name = emailProps.name;
emailInput.value = emailProps.value;
emailInput.addEventListener('input', emailProps.onChange);
emailInput.addEventListener('blur', emailProps.onBlur);
// Or with React/frameworks:
// <input {...form.getFieldProps('email')} />What just happened? getFieldProps() returned everything needed to bind an input - name, value, onChange, and onBlur - in one object!
What is form.getFieldProps()?
form.getFieldProps() is a convenience method that returns all props needed to bind an input field to form state.
Simply put, it's a one-stop-shop for getting everything an input needs - name, value, and event handlers - all at once.
Key characteristics:
- ✅ Returns
{ name, value, onChange, onBlur }object - ✅ Complete field binding in one call
- ✅ Perfect for framework integration (React, Vue, etc.)
- ✅ Handles both value updates and touch tracking
- ✅ Reduces boilerplate dramatically
- ✅ Type-safe and consistent
Syntax
// Get props for a field
const props = form.getFieldProps('fieldName');
// Returns object:
// {
// name: 'fieldName',
// value: currentValue,
// onChange: form.handleChange,
// onBlur: form.handleBlur
// }
// Use with vanilla JS
inputElement.name = props.name;
inputElement.value = props.value;
inputElement.addEventListener('input', props.onChange);
inputElement.addEventListener('blur', props.onBlur);
// Use with React
<input {...form.getFieldProps('email')} />
// Use with Vue
<input v-bind="form.getFieldProps('email')" />Parameters:
field(string) - Name of the form field
Returns: Object with properties:
name(string) - Field namevalue(any) - Current field valueonChange(function) - Change event handler (form.handleChange)onBlur(function) - Blur event handler (form.handleBlur)
Why Does This Exist?
The Challenge: Repetitive Field Binding
Without getFieldProps(), binding each field requires multiple lines of repetitive code.
const form = Forms.create({
email: '',
username: '',
password: '',
confirmPassword: ''
});
// ❌ Manual binding - repetitive pattern
emailInput.name = 'email';
emailInput.value = form.values.email;
emailInput.addEventListener('input', form.handleChange);
emailInput.addEventListener('blur', form.handleBlur);
usernameInput.name = 'username';
usernameInput.value = form.values.username;
usernameInput.addEventListener('input', form.handleChange);
usernameInput.addEventListener('blur', form.handleBlur);
passwordInput.name = 'password';
passwordInput.value = form.values.password;
passwordInput.addEventListener('input', form.handleChange);
passwordInput.addEventListener('blur', form.handleBlur);
confirmPasswordInput.name = 'confirmPassword';
confirmPasswordInput.value = form.values.confirmPassword;
confirmPasswordInput.addEventListener('input', form.handleChange);
confirmPasswordInput.addEventListener('blur', form.handleBlur);
// 4 inputs × 4 lines each = 16 lines of boilerplate!Problems: ❌ Highly repetitive - Same 4 lines for every field ❌ Error-prone - Easy to forget a line or mistype field name ❌ Maintenance burden - Update pattern everywhere when changing ❌ No reactivity - Value doesn't update automatically ❌ Framework integration - Hard to use with React/Vue
The Solution with getFieldProps()
const form = Forms.create({
email: '',
username: '',
password: '',
confirmPassword: ''
});
// ✅ One-line binding per field
const emailProps = form.getFieldProps('email');
const usernameProps = form.getFieldProps('username');
const passwordProps = form.getFieldProps('password');
const confirmPasswordProps = form.getFieldProps('confirmPassword');
// Apply props (or use with frameworks)
applyProps(emailInput, emailProps);
applyProps(usernameInput, usernameProps);
applyProps(passwordInput, passwordProps);
applyProps(confirmPasswordInput, confirmPasswordProps);
function applyProps(input, props) {
input.name = props.name;
input.value = props.value;
input.addEventListener('input', props.onChange);
input.addEventListener('blur', props.onBlur);
}
// Or with React - even simpler:
// <input {...form.getFieldProps('email')} />Benefits: ✅ One call per field - All props in one object ✅ Framework-friendly - Perfect for React/Vue spread syntax ✅ Consistent pattern - Same approach for all fields ✅ Less error-prone - Single source of truth ✅ Easier maintenance - Change once, affects all fields ✅ Cleaner code - Dramatic reduction in boilerplate
Mental Model
Think of getFieldProps() as a field binding kit - it gives you everything you need to wire up an input in one package.
Visual Flow
form.getFieldProps('email')
↓
Assembles binding kit:
↓
┌────────────────────────┐
│ Field Binding Kit │
│ ✓ name: 'email' │
│ ✓ value: current value │
│ ✓ onChange: handler │
│ ✓ onBlur: handler │
└────────────────────────┘
↓
Apply to input
↓
Fully connected field!Real-World Analogy
Without getFieldProps (Assembly Required):
You buy furniture from a store
They give you:
- Screws (in one bag)
- Wood panels (in another box)
- Instructions (separate paper)
- Tools (you need to find)
You spend hours assemblingWith getFieldProps (Pre-Assembled):
You call getFieldProps('chair')
You receive a complete, ready-to-use chair
Everything is already connected
Just place it and sit!How Does It Work?
Internal Process
// When you call:
const props = form.getFieldProps('email');
// Here's what happens internally:
function getFieldProps(field) {
1️⃣ Extract current value
const value = form.values[field]; // 'user@example.com'
2️⃣ Assemble props object
return {
name: field, // 'email'
value: value, // 'user@example.com'
onChange: form.handleChange, // Bound handler
onBlur: form.handleBlur // Bound handler
};
3️⃣ Return object ready for spreading
// { name: 'email', value: '...', onChange: fn, onBlur: fn }
}Reactivity Flow Diagram
getFieldProps('email')
↓
Read current value
↓
Create props object
↓
┌────────────────────────┐
│ Props Object │
│ name: 'email' │
│ value: (reactive) │
│ onChange: handler │
│ onBlur: handler │
└────────────────────────┘
↓
Spread onto input
↓
User types
↓
onChange(event) fires
↓
form.handleChange(event)
↓
form.values.email updates
↓
Input value updates (if reactive)Key Insight: In frameworks with reactivity (React, Vue), when form.values.email changes, the component re-renders and getFieldProps() returns the new value, keeping the input in sync!
Basic Usage
Example 1: Vanilla JavaScript
const form = Forms.create({
email: '',
password: ''
});
// Get props
const emailProps = form.getFieldProps('email');
const passwordProps = form.getFieldProps('password');
// Apply to inputs
emailInput.name = emailProps.name;
emailInput.value = emailProps.value;
emailInput.addEventListener('input', emailProps.onChange);
emailInput.addEventListener('blur', emailProps.onBlur);
passwordInput.name = passwordProps.name;
passwordInput.value = passwordProps.value;
passwordInput.addEventListener('input', passwordProps.onChange);
passwordInput.addEventListener('blur', passwordProps.onBlur);
// Update inputs when form values change
effect(() => {
emailInput.value = form.values.email;
passwordInput.value = form.values.password;
});Example 2: Helper Function for Vanilla JS
const form = Forms.create({
firstName: '',
lastName: '',
email: ''
});
// Reusable helper
function bindInput(input, fieldName) {
const props = form.getFieldProps(fieldName);
input.name = props.name;
input.addEventListener('input', props.onChange);
input.addEventListener('blur', props.onBlur);
// Keep value in sync reactively
effect(() => {
input.value = form.values[fieldName];
});
}
// Bind all inputs
bindInput(firstNameInput, 'firstName');
bindInput(lastNameInput, 'lastName');
bindInput(emailInput, 'email');Example 3: React Integration
function LoginForm() {
const form = Forms.create(
{
email: '',
password: ''
},
{
email: (value) => !value.includes('@') ? 'Invalid email' : '',
password: (value) => value.length < 8 ? 'Too short' : ''
}
);
return (
<form onSubmit={async (e) => {
e.preventDefault();
const result = await form.submit(handleLogin);
if (result.success) navigate('/dashboard');
}}>
{/* Spread props - complete binding! */}
<input type="email" {...form.getFieldProps('email')} />
{form.shouldShowError('email') && (
<span className="error">{form.getError('email')}</span>
)}
<input type="password" {...form.getFieldProps('password')} />
{form.shouldShowError('password') && (
<span className="error">{form.getError('password')}</span>
)}
<button type="submit" disabled={form.isSubmitting}>
{form.isSubmitting ? 'Logging in...' : 'Login'}
</button>
</form>
);
}Example 4: Vue Integration
<template>
<form @submit.prevent="handleSubmit">
<!-- Bind using v-bind -->
<input type="email" v-bind="form.getFieldProps('email')" />
<span v-if="form.shouldShowError('email')" class="error">
{{ form.getError('email') }}
</span>
<input type="password" v-bind="form.getFieldProps('password')" />
<span v-if="form.shouldShowError('password')" class="error">
{{ form.getError('password') }}
</span>
<button type="submit" :disabled="form.isSubmitting">
{{ form.isSubmitting ? 'Logging in...' : 'Login' }}
</button>
</form>
</template>
<script>
export default {
setup() {
const form = Forms.create(
{ email: '', password: '' },
{
email: (value) => !value.includes('@') ? 'Invalid' : '',
password: (value) => value.length < 8 ? 'Too short' : ''
}
);
async function handleSubmit() {
const result = await form.submit(loginUser);
if (result.success) router.push('/dashboard');
}
return { form, handleSubmit };
}
};
</script>Example 5: Dynamic Form Builder
const form = Forms.create({
field1: '',
field2: '',
field3: ''
});
const fields = ['field1', 'field2', 'field3'];
// Build form dynamically
fields.forEach(fieldName => {
const input = document.createElement('input');
const props = form.getFieldProps(fieldName);
input.name = props.name;
input.type = 'text';
input.addEventListener('input', props.onChange);
input.addEventListener('blur', props.onBlur);
// Keep in sync
effect(() => {
input.value = form.values[fieldName];
});
formContainer.appendChild(input);
});Advanced Patterns
Pattern 1: Custom Props Extension
const form = Forms.create({ email: '' });
function getExtendedFieldProps(field, extraProps = {}) {
const baseProps = form.getFieldProps(field);
return {
...baseProps,
...extraProps,
className: form.shouldShowError(field) ? 'error' : '',
'aria-invalid': form.hasError(field),
'aria-describedby': `${field}-error`
};
}
// Use extended props
const emailProps = getExtendedFieldProps('email', {
type: 'email',
placeholder: 'Enter your email',
autoComplete: 'email'
});
Object.assign(emailInput, emailProps);
emailInput.addEventListener('input', emailProps.onChange);
emailInput.addEventListener('blur', emailProps.onBlur);Pattern 2: Checkbox Field Props
const form = Forms.create({
newsletter: false,
terms: false
});
function getCheckboxProps(field) {
const baseProps = form.getFieldProps(field);
return {
...baseProps,
type: 'checkbox',
checked: baseProps.value, // Use 'checked' instead of 'value'
value: undefined // Remove value
};
}
// Use with checkboxes
const newsletterProps = getCheckboxProps('newsletter');
newsletterCheckbox.name = newsletterProps.name;
newsletterCheckbox.type = newsletterProps.type;
newsletterCheckbox.addEventListener('change', newsletterProps.onChange);
newsletterCheckbox.addEventListener('blur', newsletterProps.onBlur);
effect(() => {
newsletterCheckbox.checked = form.values.newsletter;
});Pattern 3: Select Field Props
const form = Forms.create({ country: 'us' });
function getSelectProps(field) {
const baseProps = form.getFieldProps(field);
return {
...baseProps,
onChange: (e) => {
baseProps.onChange(e);
// Additional logic for selects
console.log('Selected:', e.target.value);
}
};
}
const countryProps = getSelectProps('country');
countrySelect.name = countryProps.name;
countrySelect.addEventListener('change', countryProps.onChange);
countrySelect.addEventListener('blur', countryProps.onBlur);
effect(() => {
countrySelect.value = form.values.country;
});Pattern 4: Field Props with Transformers
const form = Forms.create({
phone: '',
price: 0
});
function getTransformedFieldProps(field, transformer) {
const baseProps = form.getFieldProps(field);
return {
...baseProps,
onChange: (e) => {
// Transform before updating
const transformed = transformer(e.target.value);
e.target.value = transformed;
baseProps.onChange(e);
}
};
}
// Phone number transformer
const phoneProps = getTransformedFieldProps('phone', (value) => {
const cleaned = value.replace(/\D/g, '');
const match = cleaned.match(/^(\d{3})(\d{3})(\d{4})$/);
return match ? `(${match[1]}) ${match[2]}-${match[3]}` : value;
});
phoneInput.name = phoneProps.name;
phoneInput.addEventListener('input', phoneProps.onChange);
phoneInput.addEventListener('blur', phoneProps.onBlur);Pattern 5: Disabled Field Props
const form = Forms.create({
email: '',
confirmEmail: ''
});
function getConditionalFieldProps(field, isDisabled) {
const baseProps = form.getFieldProps(field);
return {
...baseProps,
disabled: isDisabled(),
onChange: isDisabled() ? null : baseProps.onChange,
onBlur: isDisabled() ? null : baseProps.onBlur
};
}
// Disable confirmEmail until email is valid
const confirmEmailProps = getConditionalFieldProps(
'confirmEmail',
() => !form.values.email || form.hasError('email')
);
effect(() => {
confirmEmailInput.disabled = !form.values.email || form.hasError('email');
});Pattern 6: Field Props with Validation Feedback
const form = Forms.create(
{ password: '' },
{ password: (value) => value.length < 8 ? 'Too short' : '' }
);
function getValidatedFieldProps(field) {
const baseProps = form.getFieldProps(field);
return {
...baseProps,
onBlur: (e) => {
baseProps.onBlur(e);
// Show validation feedback
setTimeout(() => {
if (form.shouldShowError(field)) {
showValidationTooltip(e.target, form.getError(field));
} else if (form.values[field]) {
showSuccessIndicator(e.target);
}
}, 0);
}
};
}
const passwordProps = getValidatedFieldProps('password');Pattern 7: Multi-Field Component
function createFormField(config) {
const { label, field, type = 'text', form } = config;
const container = document.createElement('div');
container.className = 'form-field';
const labelEl = document.createElement('label');
labelEl.textContent = label;
labelEl.htmlFor = field;
const input = document.createElement('input');
const props = form.getFieldProps(field);
input.id = field;
input.name = props.name;
input.type = type;
input.addEventListener('input', props.onChange);
input.addEventListener('blur', props.onBlur);
const errorEl = document.createElement('span');
errorEl.className = 'error';
// Reactive updates
effect(() => {
input.value = form.values[field];
if (form.shouldShowError(field)) {
errorEl.textContent = form.getError(field);
errorEl.style.display = 'block';
input.classList.add('error');
} else {
errorEl.style.display = 'none';
input.classList.remove('error');
}
});
container.appendChild(labelEl);
container.appendChild(input);
container.appendChild(errorEl);
return container;
}
// Use it
const emailField = createFormField({
label: 'Email',
field: 'email',
type: 'email',
form
});
formContainer.appendChild(emailField);Pattern 8: Field Props with Analytics
const form = Forms.create({ email: '', password: '' });
function getAnalyticsFieldProps(field) {
const baseProps = form.getFieldProps(field);
return {
...baseProps,
onChange: (e) => {
baseProps.onChange(e);
analytics.track('field_changed', { field, value: e.target.value });
},
onBlur: (e) => {
baseProps.onBlur(e);
analytics.track('field_blurred', {
field,
hasError: form.hasError(field),
isEmpty: !form.values[field]
});
}
};
}
const emailProps = getAnalyticsFieldProps('email');Pattern 9: Field Props with Autocomplete
const form = Forms.create({ city: '' });
function getAutocompleteFieldProps(field, fetchSuggestions) {
const baseProps = form.getFieldProps(field);
return {
...baseProps,
onChange: async (e) => {
baseProps.onChange(e);
// Fetch autocomplete suggestions
if (e.target.value.length >= 2) {
const suggestions = await fetchSuggestions(e.target.value);
showSuggestions(e.target, suggestions);
}
}
};
}
async function fetchCities(query) {
const response = await fetch(`/api/cities?q=${query}`);
return await response.json();
}
const cityProps = getAutocompleteFieldProps('city', fetchCities);Pattern 10: Form Generator from Schema
const schema = [
{ field: 'firstName', label: 'First Name', type: 'text' },
{ field: 'lastName', label: 'Last Name', type: 'text' },
{ field: 'email', label: 'Email', type: 'email' },
{ field: 'age', label: 'Age', type: 'number' }
];
const initialValues = {
firstName: '',
lastName: '',
email: '',
age: 0
};
const form = Forms.create(initialValues);
// Generate form from schema
schema.forEach(({ field, label, type }) => {
const wrapper = document.createElement('div');
const labelEl = document.createElement('label');
labelEl.textContent = label;
const input = document.createElement('input');
input.type = type;
// Apply field props
const props = form.getFieldProps(field);
input.name = props.name;
input.addEventListener('input', props.onChange);
input.addEventListener('blur', props.onBlur);
effect(() => {
input.value = form.values[field];
});
wrapper.appendChild(labelEl);
wrapper.appendChild(input);
formContainer.appendChild(wrapper);
});Common Pitfalls
Pitfall 1: Not Updating Value Reactively
const form = Forms.create({ email: '' });
const props = form.getFieldProps('email');
// ❌ Setting value once - doesn't update
emailInput.value = props.value;
emailInput.addEventListener('input', props.onChange);
// User types, form updates, but input value doesn't sync
// ✅ Keep value in sync reactively
effect(() => {
emailInput.value = form.values.email;
});
emailInput.addEventListener('input', props.onChange);Pitfall 2: Spreading in Vanilla JS
const form = Forms.create({ email: '' });
// ❌ Spread doesn't work in vanilla JS
const props = form.getFieldProps('email');
emailInput = { ...props }; // This creates new object, doesn't set on input
// ✅ Assign properties individually
emailInput.name = props.name;
emailInput.value = props.value;
emailInput.addEventListener('input', props.onChange);
emailInput.addEventListener('blur', props.onBlur);Pitfall 3: Calling on Non-Existent Field
const form = Forms.create({ email: '' });
// ❌ Field doesn't exist in form
const props = form.getFieldProps('username');
// Returns props but field not in form.values
// ✅ Ensure field exists
if ('username' in form.values) {
const props = form.getFieldProps('username');
}Pitfall 4: Forgetting Event Listeners
const form = Forms.create({ email: '' });
const props = form.getFieldProps('email');
// ❌ Only setting name and value
emailInput.name = props.name;
emailInput.value = props.value;
// Input won't update form!
// ✅ Add event listeners
emailInput.addEventListener('input', props.onChange);
emailInput.addEventListener('blur', props.onBlur);Pitfall 5: Reusing Props Object
const form = Forms.create({ email: '' });
// ❌ Reusing same props object
const props = form.getFieldProps('email');
emailInput1.name = props.name;
emailInput2.name = props.name;
// Both inputs control same field - might be confusing
// ✅ Use unique field names
const email1Props = form.getFieldProps('email1');
const email2Props = form.getFieldProps('email2');Summary
Key Takeaways
getFieldProps()returns complete binding object - name, value, onChange, onBlur.Perfect for frameworks - works seamlessly with React/Vue spread syntax.
Reduces boilerplate - one call replaces 4+ lines of code.
Consistent pattern - same approach for all fields.
Extensible - wrap to add custom behavior.
Combines handleChange and handleBlur - complete field binding.
When to Use getFieldProps()
✅ Use getFieldProps() for:
- Framework integration (React, Vue)
- Reducing boilerplate
- Consistent field binding
- Quick form setup
- Component libraries
❌ Don't use getFieldProps() when:
- Need highly custom event handlers
- Not using name/onChange/onBlur pattern
- Binding non-standard inputs
Comparison: Manual vs getFieldProps()
| Aspect | Manual | getFieldProps() |
|---|---|---|
| Lines per field | 4+ lines | 1 line |
| Boilerplate | High | Very low |
| Framework integration | Complex | Native (spread) |
| Consistency | Manual | Automatic |
| Error-prone | Yes | No |
| Extensibility | Limited | Easy to wrap |
Returned Object Structure
form.getFieldProps('email')
// Returns:
{
name: 'email',
value: 'current@value.com',
onChange: form.handleChange,
onBlur: form.handleBlur
}Typical Usage Pattern
// 1. Create form
const form = Forms.create({ email: '' });
// 2. Get props
const props = form.getFieldProps('email');
// 3. Apply to input (vanilla JS)
emailInput.name = props.name;
emailInput.addEventListener('input', props.onChange);
emailInput.addEventListener('blur', props.onBlur);
effect(() => {
emailInput.value = form.values.email;
});
// Or with React - even simpler:
<input type="email" {...form.getFieldProps('email')} />Related Methods
handleChange(event)- Update field valuehandleBlur(event)- Mark field as touchedsetValue(field, value)- Manual value updatessetTouched(field, touched)- Manual touch updates
One-Line Rule
form.getFieldProps(field)returns everything needed to bind an input - name, value, onChange, and onBlur - in a single object, perfect for spreading in React/Vue or applying in vanilla JS.
What's Next?
- Master framework integration patterns
- Build reusable form components
- Explore advanced field binding techniques