Custom Hook de Formulario
Volver a clases
Desarrollo Móvil●●Intermedio

Custom Hook de Formulario

120 min
41 vistas

Creación de un hook reutilizable para manejar valores, errores, touched y submit.

useForm Hook

typescript
1interface 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 useForm genérico con values, errors, touched y isSubmitting.
  • Implementar handleChange, handleBlur y handleSubmit con 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.

  1. Añade soporte para validación asíncrona (ej. email ya registrado).
  2. Implementa un modo validateOnChange configurable.
  3. Agrega la opción de onSuccess y onError en el hook.
  4. Integra el hook en un formulario de login y otro de registro.

Documentación Oficial

DocumentaciónPrincipiante0

Formik

Libreria - Alternativa popular para manejo de formularios.

Documentación Oficial

DocumentaciónPrincipiante0

Yup

Librería - Validación de esquemas para formularios complejos.

Documentación Oficial

DocumentaciónPrincipiante0

React Hooks

Referencia oficial de hooks para construir lógica reutilizable.

ALVESC ACADEMY - Plataforma Educativa