Components and patterns for displaying validation errors with flexible rendering options.
Component for displaying field error messages with flexible rendering patterns.
/**
* Component for displaying field error messages
* @param props - ErrorMessage configuration
* @returns JSX element displaying error or null if no error
*/
function ErrorMessage(props: ErrorMessageProps): JSX.Element | null;
interface ErrorMessageProps {
/** Name of the field to display errors for */
name: string;
/** Component or HTML element to render error with */
component?: React.ComponentType<any> | keyof JSX.IntrinsicElements;
/** Children render function receiving error message */
children?: (error: string) => React.ReactNode;
/** Render prop function receiving error message */
render?: (error: string) => React.ReactNode;
/** CSS class name for styling */
className?: string;
/** Additional HTML attributes */
[key: string]: any;
}Usage Examples:
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";
// Basic usage with default div element
const BasicForm = () => (
<Formik
initialValues={{ email: '', password: '' }}
validationSchema={Yup.object({
email: Yup.string().email('Invalid email').required('Required'),
password: Yup.string().min(6, 'Too short').required('Required'),
})}
onSubmit={values => console.log(values)}
>
<Form>
<Field name="email" type="email" />
<ErrorMessage name="email" />
<Field name="password" type="password" />
<ErrorMessage name="password" />
<button type="submit">Submit</button>
</Form>
</Formik>
);
// With custom component
const StyledError = ({ children }) => (
<div className="error-message" style={{ color: 'red', fontSize: '12px' }}>
{children}
</div>
);
const StyledForm = () => (
<Formik
initialValues={{ username: '' }}
validate={values => {
const errors = {};
if (!values.username) {
errors.username = 'Username is required';
}
return errors;
}}
onSubmit={values => console.log(values)}
>
<Form>
<Field name="username" />
<ErrorMessage name="username" component={StyledError} />
<button type="submit">Submit</button>
</Form>
</Formik>
);
// With render prop
const RenderPropForm = () => (
<Formik
initialValues={{ age: '' }}
validate={values => {
const errors = {};
if (!values.age) {
errors.age = 'Age is required';
} else if (values.age < 18) {
errors.age = 'Must be 18 or older';
}
return errors;
}}
onSubmit={values => console.log(values)}
>
<Form>
<Field name="age" type="number" />
<ErrorMessage
name="age"
render={error => (
<div className="alert alert-danger">
<strong>Error:</strong> {error}
</div>
)}
/>
<button type="submit">Submit</button>
</Form>
</Formik>
);
// With children function
const ChildrenFunctionForm = () => (
<Formik
initialValues={{ phone: '' }}
validate={values => {
const errors = {};
if (values.phone && !/^\d{10}$/.test(values.phone)) {
errors.phone = 'Phone must be 10 digits';
}
return errors;
}}
onSubmit={values => console.log(values)}
>
<Form>
<Field name="phone" placeholder="Phone number" />
<ErrorMessage name="phone">
{error => (
<div className="error-tooltip">
<span className="icon">⚠️</span>
<span className="message">{error}</span>
</div>
)}
</ErrorMessage>
<button type="submit">Submit</button>
</Form>
</Formik>
);
// With HTML element and styling
const HtmlElementForm = () => (
<Formik
initialValues={{ website: '' }}
validate={values => {
const errors = {};
if (values.website && !/^https?:\/\/.+/.test(values.website)) {
errors.website = 'Website must be a valid URL';
}
return errors;
}}
onSubmit={values => console.log(values)}
>
<Form>
<Field name="website" placeholder="Website URL" />
<ErrorMessage
name="website"
component="span"
className="field-error"
style={{ color: 'red', fontStyle: 'italic' }}
/>
<button type="submit">Submit</button>
</Form>
</Formik>
);Common patterns for displaying and handling form errors.
Field-Level Error Display:
import { useField } from "formik";
// Custom field component with integrated error display
const TextInputWithError = ({ label, ...props }) => {
const [field, meta] = useField(props);
return (
<div className="form-field">
<label htmlFor={props.id || props.name}>{label}</label>
<input {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
);
};
// Usage
const CustomFieldForm = () => (
<Formik
initialValues={{ firstName: '', lastName: '' }}
validate={values => {
const errors = {};
if (!values.firstName) errors.firstName = 'Required';
if (!values.lastName) errors.lastName = 'Required';
return errors;
}}
onSubmit={values => console.log(values)}
>
<Form>
<TextInputWithError
label="First Name"
name="firstName"
type="text"
/>
<TextInputWithError
label="Last Name"
name="lastName"
type="text"
/>
<button type="submit">Submit</button>
</Form>
</Formik>
);Form-Level Error Summary:
const FormWithErrorSummary = () => (
<Formik
initialValues={{ name: '', email: '', phone: '' }}
validate={values => {
const errors = {};
if (!values.name) errors.name = 'Name is required';
if (!values.email) errors.email = 'Email is required';
if (!values.phone) errors.phone = 'Phone is required';
return errors;
}}
onSubmit={values => console.log(values)}
>
{({ errors, touched, isValid }) => (
<Form>
{/* Error Summary */}
{!isValid && Object.keys(touched).length > 0 && (
<div className="error-summary">
<h4>Please fix the following errors:</h4>
<ul>
{Object.entries(errors).map(([field, error]) => (
touched[field] && <li key={field}>{error}</li>
))}
</ul>
</div>
)}
<Field name="name" placeholder="Name" />
<ErrorMessage name="name" component="div" className="field-error" />
<Field name="email" type="email" placeholder="Email" />
<ErrorMessage name="email" component="div" className="field-error" />
<Field name="phone" placeholder="Phone" />
<ErrorMessage name="phone" component="div" className="field-error" />
<button type="submit">Submit</button>
</Form>
)}
</Formik>
);Async Validation Errors:
const AsyncValidationForm = () => (
<Formik
initialValues={{ username: '' }}
validate={async (values) => {
const errors = {};
if (!values.username) {
errors.username = 'Username is required';
} else if (values.username.length < 3) {
errors.username = 'Username must be at least 3 characters';
} else {
// Simulate async validation
try {
const available = await checkUsernameAvailability(values.username);
if (!available) {
errors.username = 'Username is already taken';
}
} catch (error) {
errors.username = 'Error checking username availability';
}
}
return errors;
}}
onSubmit={values => console.log(values)}
>
{({ isValidating }) => (
<Form>
<Field name="username" placeholder="Username" />
{isValidating && <span>Checking availability...</span>}
<ErrorMessage name="username" component="div" className="error" />
<button type="submit">Submit</button>
</Form>
)}
</Formik>
);
const checkUsernameAvailability = async (username) => {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
return !['admin', 'user', 'test'].includes(username.toLowerCase());
};Server Error Handling:
const ServerErrorForm = () => (
<Formik
initialValues={{ email: '', password: '' }}
onSubmit={async (values, { setSubmitting, setFieldError, setStatus }) => {
try {
await loginUser(values);
setStatus({ type: 'success', message: 'Login successful!' });
} catch (error) {
if (error.field) {
// Field-specific error
setFieldError(error.field, error.message);
} else {
// General error
setStatus({ type: 'error', message: error.message });
}
} finally {
setSubmitting(false);
}
}}
>
{({ status, isSubmitting }) => (
<Form>
{/* Global status messages */}
{status && (
<div className={`alert alert-${status.type}`}>
{status.message}
</div>
)}
<Field name="email" type="email" placeholder="Email" />
<ErrorMessage name="email" component="div" className="error" />
<Field name="password" type="password" placeholder="Password" />
<ErrorMessage name="password" component="div" className="error" />
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Logging in...' : 'Login'}
</button>
</Form>
)}
</Formik>
);
const loginUser = async (credentials) => {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
if (credentials.email === 'taken@example.com') {
throw { field: 'email', message: 'Email already registered' };
}
if (credentials.password === 'wrong') {
throw { field: 'password', message: 'Incorrect password' };
}
if (!credentials.email || !credentials.password) {
throw { message: 'Please fill in all fields' };
}
return { success: true };
};Array Field Errors:
const ArrayErrorForm = () => (
<Formik
initialValues={{ emails: [''] }}
validate={values => {
const errors = {};
if (!values.emails || values.emails.length === 0) {
errors.emails = 'At least one email is required';
} else {
const emailErrors = [];
values.emails.forEach((email, index) => {
if (!email) {
emailErrors[index] = 'Email is required';
} else if (!/\S+@\S+\.\S+/.test(email)) {
emailErrors[index] = 'Invalid email format';
}
});
if (emailErrors.length > 0) {
errors.emails = emailErrors;
}
}
return errors;
}}
onSubmit={values => console.log(values)}
>
{({ values, errors, touched }) => (
<Form>
<FieldArray name="emails">
{({ push, remove }) => (
<div>
{values.emails.map((email, index) => (
<div key={index}>
<Field name={`emails.${index}`} placeholder="Email" />
{/* Individual field error */}
{errors.emails &&
errors.emails[index] &&
touched.emails &&
touched.emails[index] && (
<div className="error">{errors.emails[index]}</div>
)}
<button type="button" onClick={() => remove(index)}>
Remove
</button>
</div>
))}
<button type="button" onClick={() => push('')}>
Add Email
</button>
</div>
)}
</FieldArray>
{/* Array-level error */}
{typeof errors.emails === 'string' && (
<div className="error">{errors.emails}</div>
)}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
);TypeScript interfaces for error handling.
type FormikErrors<Values> = {
[K in keyof Values]?: Values[K] extends any[]
? Values[K][number] extends object
? FormikErrors<Values[K][number]>[] | string | string[]
: string | string[]
: Values[K] extends object
? FormikErrors<Values[K]>
: string;
};
interface ErrorMessageProps {
/** Field name to display errors for */
name: string;
/** Component to render error with */
component?: React.ComponentType<any> | keyof JSX.IntrinsicElements;
/** Children render function */
children?: (error: string) => React.ReactNode;
/** Render prop function */
render?: (error: string) => React.ReactNode;
/** CSS class name */
className?: string;
/** Additional props */
[key: string]: any;
}