
Creación de un hook reutilizable para manejar valores, errores, touched y submit.
useForm Hook
typescript1interface UseFormOptions<T> { 2 initialValues: T; 3 validate?: (values: T) => Partial<Record<keyof T, string>>; 4 onSubmit: (values: T) => void | Promise<void>; 5} 6 7function useForm<T extends Record<string, any>>({ 8 initialValues, 9 validate, 10 onSubmit, 11}: UseFormOptions<T>) { 12 const [values, setValues] = useState<T>(initialValues); 13 const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({}); 14 const [touched, setTouched] = useState<Partial<Record<keyof T, boolean>>>({}); 15 const [isSubmitting, setIsSubmitting] = useState(false); 16 17 const handleChange = (field: keyof T) => (value: any) => { 18 setValues((prev) => ({ ...prev, [field]: value })); 19 20 if (errors[field]) { 21 setErrors((prev) => ({ ...prev, [field]: undefined })); 22 } 23 }; 24 25 const handleBlur = (field: keyof T) => () => { 26 setTouched((prev) => ({ ...prev, [field]: true })); 27 28 if (validate) { 29 const fieldErrors = validate(values); 30 if (fieldErrors[field]) { 31 setErrors((prev) => ({ ...prev, [field]: fieldErrors[field] })); 32 } 33 } 34 }; 35 36 const handleSubmit = async () => { 37 // Marcar todos como touched 38 const allTouched = Object.keys(values).reduce( 39 (acc, key) => ({ 40 ...acc, 41 [key]: true, 42 }), 43 {} 44 ); 45 setTouched(allTouched); 46 47 // Validar 48 if (validate) { 49 const validationErrors = validate(values); 50 setErrors(validationErrors); 51 52 if (Object.keys(validationErrors).length > 0) { 53 return; 54 } 55 } 56 57 // Enviar 58 setIsSubmitting(true); 59 try { 60 await onSubmit(values); 61 } catch (error) { 62 console.error("Error al enviar:", error); 63 } finally { 64 setIsSubmitting(false); 65 } 66 }; 67 68 const reset = () => { 69 setValues(initialValues); 70 setErrors({}); 71 setTouched({}); 72 setIsSubmitting(false); 73 }; 74 75 return { 76 values, 77 errors, 78 touched, 79 isSubmitting, 80 handleChange, 81 handleBlur, 82 handleSubmit, 83 reset, 84 }; 85} 86 87// Uso del hook 88interface LoginForm { 89 email: string; 90 password: string; 91} 92 93function LoginScreen() { 94 const form = useForm<LoginForm>({ 95 initialValues: { 96 email: "", 97 password: "", 98 }, 99 validate: (values) => { 100 const errors: Partial<Record<keyof LoginForm, string>> = {}; 101 102 if (!values.email) { 103 errors.email = "Email requerido"; 104 } else if (!/\S+@\S+\.\S+/.test(values.email)) { 105 errors.email = "Email inválido"; 106 } 107 108 if (!values.password) { 109 errors.password = "Contraseña requerida"; 110 } else if (values.password.length < 6) { 111 errors.password = "Mínimo 6 caracteres"; 112 } 113 114 return errors; 115 }, 116 onSubmit: async (values) => { 117 // Simular login 118 await new Promise((resolve) => setTimeout(resolve, 2000)); 119 console.log("Login:", values); 120 Alert.alert("Éxito", "Login correcto"); 121 }, 122 }); 123 124 return ( 125 <KeyboardAvoidingView 126 behavior={Platform.OS === "ios" ? "padding" : "height"} 127 style={styles.container} 128 > 129 <View style={styles.formContainer}> 130 <Text style={styles.title}>Iniciar Sesión</Text> 131 132 <View style={styles.inputContainer}> 133 <TextInput 134 style={[ 135 styles.input, 136 form.touched.email && form.errors.email && styles.inputError, 137 ]} 138 value={form.values.email} 139 onChangeText={form.handleChange("email")} 140 onBlur={form.handleBlur("email")} 141 placeholder="Email" 142 keyboardType="email-address" 143 autoCapitalize="none" 144 editable={!form.isSubmitting} 145 /> 146 {form.touched.email && form.errors.email && ( 147 <Text style={styles.errorText}>{form.errors.email}</Text> 148 )} 149 </View> 150 151 <View style={styles.inputContainer}> 152 <TextInput 153 style={[ 154 styles.input, 155 form.touched.password && 156 form.errors.password && 157 styles.inputError, 158 ]} 159 value={form.values.password} 160 onChangeText={form.handleChange("password")} 161 onBlur={form.handleBlur("password")} 162 placeholder="Contraseña" 163 secureTextEntry 164 editable={!form.isSubmitting} 165 /> 166 {form.touched.password && form.errors.password && ( 167 <Text style={styles.errorText}>{form.errors.password}</Text> 168 )} 169 </View> 170 171 <TouchableOpacity 172 style={[styles.button, form.isSubmitting && styles.buttonDisabled]} 173 onPress={form.handleSubmit} 174 disabled={form.isSubmitting} 175 > 176 {form.isSubmitting ? ( 177 <ActivityIndicator color="white" /> 178 ) : ( 179 <Text style={styles.buttonText}>Iniciar Sesión</Text> 180 )} 181 </TouchableOpacity> 182 </View> 183 </KeyboardAvoidingView> 184 ); 185}
Ejemplos de Código
3 ejemplos
Hook genérico de formulario
typescript
1```typescript
2const form = useForm({
3 initialValues: { email: "", password: "" },
4 onSubmit: async (values) => console.log(values),
5});
6```Botón con estado de envío
typescript
1```typescript
2<TouchableOpacity
3 onPress={form.handleSubmit}
4 disabled={form.isSubmitting}
5/>
6```Reset del formulario
typescript
1```typescript
2const handleCancel = () => {
3 form.reset();
4};
5```Recursos
5 recursos disponibles
¡Hora de Practicar!
PrácticaIntermedio15 min
Práctica
Practiquemos.
- Crear un
useFormgenérico convalues,errors,touchedyisSubmitting. - Implementar
handleChange,handleBluryhandleSubmitcon validación opcional. - Deshabilitar inputs y botones mientras se envía el formulario.
- Agregar
reset()para volver al estado inicial.
Desafío de Código
EjercicioIntermedio15 min
Ejercicios
Realiza los ejercicios en el proyecto de la practica anterior.
- Añade soporte para validación asíncrona (ej. email ya registrado).
- Implementa un modo
validateOnChangeconfigurable. - Agrega la opción de
onSuccessyonErroren el hook. - Integra el hook en un formulario de login y otro de registro.
Documentación Oficial
Documentación Oficial
Documentación Oficial
DocumentaciónPrincipiante0
React Hooks
Referencia oficial de hooks para construir lógica reutilizable.