Build forms in React, without the tears
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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;
}