Form Validation Examples
FormInput makes form validation straightforward with its built-in error handling capabilities. These examples demonstrate common validation patterns you can implement with React Native Forminput.
Basic Form Validation
Simple validation on submission with error messages. This example shows basic form validation for email and password fields. Validation occurs both while typing and on form submission. The hasError
prop (in the core
group) activates the error state, and errorText
(in the text
group) provides the error message.
import { useState } from 'react';
import { View, Button, Text, StyleSheet } from 'react-native';
import { FormInput } from '@react-native-utils/forminput';
const BasicValidationExample = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [emailError, setEmailError] = useState(false);
const [passwordError, setPasswordError] = useState(false);
// Simple email validation regex
const emailRegex = /\S+@\S+\.\S+/;
const validateEmail = (text) => {
setEmail(text);
setEmailError(text.length > 0 && !emailRegex.test(text));
};
const validatePassword = (text) => {
setPassword(text);
setPasswordError(text.length > 0 && text.length < 8);
};
const handleSubmit = () => {
const isEmailValid = email.length > 0 && emailRegex.test(email);
const isPasswordValid = password.length >= 8;
setEmailError(!isEmailValid);
setPasswordError(!isPasswordValid);
if (isEmailValid && isPasswordValid) {
console.log('Form submitted!', { email, password });
// Process form submission
}
};
return (
<View style={styles.container}>
<FormInput
text={{
labelText: "Email",
placeholderText: "Enter your email address",
value: email,
errorText: "Please enter a valid email address",
}}
icon={{
leftIcon: "envelope",
}}
core={{
onTextChange: validateEmail,
keyboardType: "email-address",
autoCapitalize: "none",
hasError: emailError,
}}
/>
<View style={styles.spacing} />
<FormInput
text={{
labelText: "Password",
placeholderText: "Enter your password",
value: password,
errorText: "Password must be at least 8 characters long",
}}
icon={{
leftIcon: "lock",
}}
core={{
onTextChange: validatePassword,
secureTextEntry: true,
hasError: passwordError,
}}
/>
<View style={styles.spacing} />
<Button title="Submit" onPress={handleSubmit} />
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 16,
},
spacing: {
height: 16,
},
});
Live Validation with Requirements List
Real-time password validation with a requirements checklist. This example demonstrates live validation with visual feedback for complex password requirements. As the user types, each requirement is checked and marked as satisfied or not. This provides clear guidance on what needs to be fixed.
import { useState, useEffect } from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
import { FormInput } from '@react-native-utils/forminput';
const LiveValidationExample = () => {
const [username, setUsername] = useState('');
const [usernameError, setUsernameError] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [passwordErrors, setPasswordErrors] = useState([]);
const [passwordMatch, setPasswordMatch] = useState(true);
const [formSubmitted, setFormSubmitted] = useState(false);
// Username validation
useEffect(() => {
if (username.length === 0) {
setUsernameError('');
} else if (username.length < 4) {
setUsernameError('Username must be at least 4 characters');
} else if (!/^[a-zA-Z0-9_]+$/.test(username)) {
setUsernameError('Username can only contain letters, numbers, and underscores');
} else {
setUsernameError('');
}
}, [username]);
// Password validation
useEffect(() => {
const errors = [];
if (password.length > 0) {
if (password.length < 8) {
errors.push('At least 8 characters long');
}
if (!/[A-Z]/.test(password)) {
errors.push('At least one uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('At least one lowercase letter');
}
if (!/[0-9]/.test(password)) {
errors.push('At least one number');
}
if (!/[^A-Za-z0-9]/.test(password)) {
errors.push('At least one special character');
}
}
setPasswordErrors(errors);
}, [password]);
// Confirm password validation
useEffect(() => {
if (confirmPassword.length > 0 || formSubmitted) {
setPasswordMatch(password === confirmPassword);
} else {
setPasswordMatch(true);
}
}, [password, confirmPassword, formSubmitted]);
const handleSubmit = () => {
setFormSubmitted(true);
const isUsernameValid = username.length >= 4 && /^[a-zA-Z0-9_]+$/.test(username);
const isPasswordValid = passwordErrors.length === 0 && password.length > 0;
const isConfirmPasswordValid = password === confirmPassword;
if (isUsernameValid && isPasswordValid && isConfirmPasswordValid) {
console.log('Form submitted successfully!');
// Process form submission
}
};
return (
<View style={styles.container}>
{/* Username field */}
<FormInput
text={{
labelText: "Username",
placeholderText: "Create a username",
value: username,
errorText: usernameError,
}}
icon={{
leftIcon: "user",
}}
core={{
onTextChange: setUsername,
autoCapitalize: "none",
hasError: usernameError.length > 0,
}}
/>
<View style={styles.spacing} />
{/* Password field with validation list */}
<FormInput
text={{
labelText: "Password",
placeholderText: "Create a password",
value: password,
}}
icon={{
leftIcon: "lock",
}}
core={{
onTextChange: setPassword,
secureTextEntry: true,
hasError: formSubmitted && passwordErrors.length > 0,
}}
/>
{/* Password validation requirements */}
<View style={styles.requirementsList}>
{['At least 8 characters long',
'At least one uppercase letter',
'At least one lowercase letter',
'At least one number',
'At least one special character'].map((requirement, index) => {
const isMet = !passwordErrors.includes(requirement);
return (
<View key={index} style={styles.requirementRow}>
<Text style={[styles.bulletPoint, isMet && styles.validRequirement]}>
{isMet ? '✓' : '•'}
</Text>
<Text style={[styles.requirementText, isMet && styles.validRequirementText]}>
{requirement}
</Text>
</View>
);
})}
</View>
<View style={styles.spacing} />
{/* Confirm password field */}
<FormInput
text={{
labelText: "Confirm Password",
placeholderText: "Confirm your password",
value: confirmPassword,
errorText: "Passwords do not match",
}}
icon={{
leftIcon: "lock",
}}
core={{
onTextChange: setConfirmPassword,
secureTextEntry: true,
hasError: !passwordMatch,
}}
/>
<View style={styles.spacing} />
<Button title="Create Account" onPress={handleSubmit} />
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 16,
},
spacing: {
height: 16,
},
requirementsList: {
marginTop: 8,
marginLeft: 16,
},
requirementRow: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: 2,
},
bulletPoint: {
fontSize: 14,
color: '#9CA3AF',
marginRight: 8,
},
requirementText: {
fontSize: 12,
color: '#9CA3AF',
},
validRequirement: {
color: '#10B981',
},
validRequirementText: {
color: '#10B981',
},
});
Asynchronous Validation
Username and email validation with server-side checks. This example shows how to perform asynchronous validation, such as checking if a username or email is already taken. It uses the right icon to show the validation state (loading spinner, error, or success).
import { useState } from 'react';
import { View, StyleSheet, Button, ActivityIndicator } from 'react-native';
import { FormInput } from '@react-native-utils/forminput';
const AsyncValidationExample = () => {
const [username, setUsername] = useState('');
const [usernameError, setUsernameError] = useState('');
const [isCheckingUsername, setIsCheckingUsername] = useState(false);
const [email, setEmail] = useState('');
const [emailError, setEmailError] = useState('');
const [isCheckingEmail, setIsCheckingEmail] = useState(false);
// Simulate checking username availability
const checkUsernameAvailability = async (username) => {
if (username.length < 3) return;
setIsCheckingUsername(true);
try {
// Simulate API call with timeout
await new Promise(resolve => setTimeout(resolve, 1000));
// For demonstration: usernames containing "admin" are considered taken
const isAvailable = !username.toLowerCase().includes('admin');
if (!isAvailable) {
setUsernameError('This username is already taken');
} else {
setUsernameError('');
}
} catch (error) {
console.error('Error checking username:', error);
setUsernameError('Error checking username availability');
} finally {
setIsCheckingUsername(false);
}
};
// Simulate checking email availability
const checkEmailAvailability = async (email) => {
if (!email.match(/\S+@\S+\.\S+/)) return;
setIsCheckingEmail(true);
try {
// Simulate API call with timeout
await new Promise(resolve => setTimeout(resolve, 1000));
// For demonstration: emails containing "test" are considered taken
const isAvailable = !email.toLowerCase().includes('test');
if (!isAvailable) {
setEmailError('This email is already registered');
} else {
setEmailError('');
}
} catch (error) {
console.error('Error checking email:', error);
setEmailError('Error checking email availability');
} finally {
setIsCheckingEmail(false);
}
};
const handleUsernameChange = (text) => {
setUsername(text);
if (text.length < 3) {
setUsernameError('Username must be at least 3 characters');
} else {
setUsernameError('');
checkUsernameAvailability(text);
}
};
const handleEmailChange = (text) => {
setEmail(text);
if (!text.match(/\S+@\S+\.\S+/)) {
setEmailError('Please enter a valid email address');
} else {
setEmailError('');
checkEmailAvailability(text);
}
};
return (
<View style={styles.container}>
<FormInput
text={{
labelText: "Username",
placeholderText: "Choose a username",
value: username,
errorText: usernameError,
}}
icon={{
leftIcon: "user",
rightIcon: isCheckingUsername ? "spinner" : usernameError ? "times-circle" : username.length >= 3 ? "check-circle" : undefined,
rightIconColor: usernameError ? "#EF4444" : "#10B981",
}}
core={{
onTextChange: handleUsernameChange,
hasError: usernameError.length > 0,
autoCapitalize: "none",
}}
/>
<View style={styles.spacing} />
<FormInput
text={{
labelText: "Email",
placeholderText: "Enter your email",
value: email,
errorText: emailError,
}}
icon={{
leftIcon: "envelope",
rightIcon: isCheckingEmail ? "spinner" : emailError ? "times-circle" : email.match(/\S+@\S+\.\S+/) ? "check-circle" : undefined,
rightIconColor: emailError ? "#EF4444" : "#10B981",
}}
core={{
onTextChange: handleEmailChange,
hasError: emailError.length > 0,
keyboardType: "email-address",
autoCapitalize: "none",
}}
/>
<View style={styles.spacing} />
<Button
title="Register"
disabled={isCheckingUsername || isCheckingEmail || usernameError.length > 0 || emailError.length > 0 || username.length === 0 || email.length === 0}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
padding: 16,
},
spacing: {
height: 16,
},
});
Validation Best Practices
1. Provide Clear Error Messages
Use specific, actionable error messages that tell users exactly what they need to fix. For example, instead of "Invalid password", use "Password must be at least 8 characters long".
2. Validate at the Right Time
Consider when validation should occur:
- On blur: Validate when the user leaves the field
- On change: Validate as the user types (good for instant feedback)
- On submit: Validate all fields when the form is submitted
Often, a combination of these approaches works best.
3. Use Visual Indicators
FormInput has built-in error styling, but you can enhance this with icons:
- Use the right icon slot to show success (checkmark) or error (X) icons
- Use color to differentiate states (red for errors, green for valid)
- Show loading indicators during asynchronous validation
4. Accessibility Considerations
Make sure your form validation is accessible:
- Use the
accessibilityLabel
andaccessibilityHint
props in thecomponentProps
group - Ensure error messages are descriptive and helpful
- Don't rely solely on color to indicate errors