Skip to main content

Field Component

The Field component wraps your input components and connects them to the form state. It handles validation, error display, and value synchronization.

Import

import { Field } from 'efx-forms';

Type Signature

interface FieldProps {
// Required
name: string;
Field: ComponentType<any>;

// Optional
initialValue?: IValue;
parse?: (value: IValue) => IValue;
format?: (value: IValue) => IValue;
passive?: boolean;
validators?: TFieldValidator[];
validateOnBlur?: boolean;
validateOnChange?: boolean;
disableFieldReinit?: boolean;
formName?: string;

// Passed to wrapped component
label?: string;
error?: string;
errors?: string[];
value?: IValue;
onChange?: (value: IValue) => void;
onBlur?: (value: IValue) => void;
[key: string]: any;
}

Props

PropTypeDefaultRequiredDescription
namestring-✅ YesField name. Used to register field in the form
FieldComponentType-✅ YesInput component to render
initialValueany''NoInitial value. Used on load and reset
parse(value) => any-NoTransform value before storing
format(value) => any-NoFormat value before displaying
passivebooleanfalseNoPassive field doesn't update active state
validatorsvalidator[]-NoField-level validators. Takes priority over form validators
validateOnBlurbooleantrueNoValidate on blur. Overrides form setting
validateOnChangebooleanfalseNoValidate on change. Overrides form setting
disableFieldReinitboolean-NoDisable reinit when initialValue changes
formNamestring-NoForm name if used outside context or different form

Basic Usage

import { Field } from 'efx-forms';

const Input = ({ label, error, value, onChange, onBlur, ...props }) => (
<div>
<label htmlFor={props.id}>{label}</label>
<input
id={props.id}
value={value || ''}
onChange={(e) => onChange(e.target.value)}
onBlur={(e) => onBlur(e.target.value)}
{...props}
/>
{error && <span className="error">{error}</span>}
</div>
);

function MyField() {
return (
<Field
name="email"
Field={Input}
label="Email"
type="email"
/>
);
}

Create Reusable Field Components

Wrap Field to create reusable form fields:

import { Field } from 'efx-forms';
import { required, email } from 'efx-forms/validators';

const Input = ({ label, error, value, onChange, onBlur, ...props }) => (
<div>
<label>{label}</label>
<input value={value || ''} onChange={onChange} onBlur={onBlur} {...props} />
{error && <span>{error}</span>}
</div>
);

// Reusable TextField
const TextField = (props) => <Field Field={Input} {...props} />;

// Usage with validators
<TextField
name="email"
label="Email"
type="email"
validators={[
required({ msg: 'Email is required' }),
email(),
]}
/>

Value Transformation

Parse (before storing)

<Field
name="price"
Field={Input}
type="number"
parse={(value) => parseFloat(value) || 0}
/>

Format (before displaying)

<Field
name="price"
Field={Input}
format={(value) => `$${Number(value).toFixed(2)}`}
/>

Combined Parse and Format

<Field
name="date"
Field={Input}
parse={(value) => new Date(value).toISOString()}
format={(value) => value ? new Date(value).toLocaleDateString() : ''}
/>

Field-Level Validation

import { required, email, min, max } from 'efx-forms/validators';

<Field
name="email"
Field={Input}
label="Email"
validators={[
required({ msg: 'Email is required' }),
email(),
]}
/>

<Field
name="age"
Field={Input}
label="Age"
type="number"
validators={[
min(18, { msg: 'Must be 18+' }),
max(120, { msg: 'Invalid age' }),
]}
/>

Validation Behavior Override

Override form-level validation settings per field:

<Form name="my-form" validateOnBlur={false} validateOnChange={false}>
{/* This field validates on blur despite form settings */}
<Field
name="email"
Field={Input}
validateOnBlur={true}
validateOnChange={false}
/>

{/* This field validates on change */}
<Field
name="password"
Field={Input}
validateOnBlur={false}
validateOnChange={true}
/>
</Form>

Initial Value

<Field
name="username"
Field={Input}
initialValue="John Doe"
/>

Note: Field initialValue takes priority over form initialValues.

Passive Field

Passive fields don't update their active state:

<Field
name="readonly-field"
Field={Input}
passive={true}
initialValue="Read-only value"
/>

Access Field Data

Using FieldDataProvider

import { FieldDataProvider } from 'efx-forms/FieldDataProvider';

<Field name="username" Field={Input} />

<FieldDataProvider name="username">
{({ value, active, error, errors }) => (
<div>
<p>Current value: {value}</p>
<p>Active: {active ? 'yes' : 'no'}</p>
{error && <p>Error: {error}</p>}
</div>
)}
</FieldDataProvider>

Using Hooks

import { useField, useFieldData } from 'efx-forms';

function MyComponent() {
const field = useField('username');
const data = useFieldData('username');

return (
<div>
<p>Value: {data.value}</p>
<p>Error: {data.error}</p>
</div>
);
}

Using Outside Form Context

Specify formName when using field outside its form:

// Field used outside form context
<Field
name="username"
Field={Input}
formName="registration-form"
/>

Complete Example

import { Form, Field } from 'efx-forms';
import { FormDataProvider } from 'efx-forms/FormDataProvider';
import { required, email } from 'efx-forms/validators';

const Input = ({ label, error, value, onChange, onBlur, ...props }) => (
<div className="form-field">
<label htmlFor={props.id}>{label}</label>
<input
id={props.id}
value={value || ''}
onChange={(e) => onChange(e.target.value)}
onBlur={(e) => onBlur(e.target.value)}
{...props}
/>
{error && <span className="error">{error}</span>}
</div>
);

const TextField = (props) => <Field Field={Input} {...props} />;

function RegistrationForm() {
const handleSubmit = async (values) => {
console.log('Submitting:', values);
};

return (
<Form
name="registration-form"
onSubmit={handleSubmit}
validators={{
name: [required()],
}}
>
<TextField
name="name"
label="Full Name"
validators={[required()]}
/>

<TextField
name="email"
label="Email"
type="email"
validators={[
required({ msg: 'Email is required' }),
email(),
]}
/>

<TextField
name="age"
label="Age"
type="number"
parse={(v) => parseInt(v, 10) || 0}
validators={[
required(),
(v) => v >= 18 || 'Must be 18 or older',
]}
/>

<FormDataProvider>
{({ values, errors }) => (
<pre>{JSON.stringify({ values, errors }, null, 2)}</pre>
)}
</FormDataProvider>

<button type="submit">Register</button>
</Form>
);
}