Skip to content

React Hooks Formularios

En esta sección aprenderemos a trabajar con formularios en React, utilizando Hooks.

⭐ Videos Premium ⭐

Esta sección es parte del curso en Udemy. Si quieres acceder a ella, puedes comprar el curso en Udemy: React + Firebase by bluuweb.

Planificación

  • Formularios no controlados
  • Formularios controlados

Código final github

Recursos

Extensiones Navegador (React Developer Tools):

Formulario no controlado

  • uncontrolled: En la mayoría de los casos, te recomendamos usar Componentes controlados para implementar formularios.
  • En un componente controlado, los datos del formulario son manejados por un componente React.
  • La alternativa son los componentes no controlados, donde los datos del formulario son manejados por el propio DOM.
  • Para escribir un componente no controlado, puedes usar una referencia para que obtengas los valores del formulario desde el DOM.

Referencias y el DOM

  • refs: Las referencias proporcionan una forma de acceder a los nodos del DOM o a elementos React creados en el método de renderizado.
  • useRef: useRef devuelve un objeto ref mutable cuya propiedad .current se inicializa con el argumento pasado (initialValue). El objeto devuelto se mantendrá persistente durante la vida completa del componente.
jsx
import { useRef } from "react";

const FormularioNoControlado = () => {
  const formulario = useRef(null);

  // Formulario no controlado
  const handleSubmit = (e) => {
    console.log(formulario.current);
    e.preventDefault();
  };

  return (
    <div className="container mt-2">
      <form
        onSubmit={handleSubmit}
        ref={formulario}
      >
        <input
          className="form-control mb-2"
          type="text"
          placeholder="Ingrese un TODO"
          name="todoNombre"
          defaultValue="Tarea #01"
        />
        <textarea
          className="form-control mb-2"
          type="text"
          placeholder="Ingrese un TODO"
          name="todoDescripcion"
          defaultValue="Descripción tarea #01"
        />
        <select
          className="form-control mb-2"
          name="todoEstado"
          defaultValue="completado"
        >
          <option value="pendiente">Pendiente</option>
          <option value="completado">Completado</option>
        </select>
        <button
          className="btn btn-primary"
          type="submit"
        >
          Agregar
        </button>
      </form>
    </div>
  );
};

export default FormularioNoControlado;

FormData

jsx
import { useRef } from "react";

const FormularioNoControlado = () => {
  const formulario = useRef(null);

  // Formulario no controlado
  const handleSubmit = (e) => {
    // console.log(formulario.current);
    e.preventDefault();

    const datos = new FormData(formulario.current);

    // spread operator: permite a un elemento iterable ser expandido
    // copia cada uno de sus elementos
    console.log(...datos.entries());

    // El método Object.fromEntries() transforma una lista de pares con [clave-valor] en un objeto.
    const objetoDatos = Object.fromEntries([...datos.entries()]);

    // console.log(objetoDatos);
    if (!objetoDatos.todoNombre.trim()) {
      return console.log("vacío");
    }

    console.log("pasó las validaciones!");
    formulario.current.reset();
  };

  return (
    <div className="container mt-2">
      <form
        onSubmit={handleSubmit}
        ref={formulario}
      >
        <input
          className="form-control mb-2"
          type="text"
          placeholder="Ingrese un TODO"
          name="todoNombre"
          // defaultValue="Tarea #01"
        />
        <textarea
          className="form-control mb-2"
          type="text"
          placeholder="Ingrese un TODO"
          name="todoDescripcion"
          // defaultValue="Descripción tarea #01"
        />
        <select
          className="form-control mb-2"
          name="todoEstado"
          // defaultValue="completado"
        >
          <option value="pendiente">Pendiente</option>
          <option value="completado">Completado</option>
        </select>
        <button
          className="btn btn-primary"
          type="submit"
        >
          Agregar
        </button>
      </form>
    </div>
  );
};

export default FormularioNoControlado;

Formularios controlados

  • controlled
  • Los componentes React que rendericen un formulario también controlan lo que pasa en ese formulario con las subsecuentes entradas del usuario.
  • Ahora vamos a poder detectar los campos input en tiempo real.

Ejemplo casi casi:

jsx
import { useState } from "react";

const Form = () => {
  const [todo, setTodo] = useState({
    todoNombre: "",
    todoDescripcion: "",
    todoEstado: "pendiente",
  });

  const handleSubmit = (e) => {
    e.preventDefault();

    console.log(todo);
  };

  return (
    <div className="container mt-2">
      <form onSubmit={handleSubmit}>
        <input
          className="form-control mb-2"
          type="text"
          placeholder="Ingrese un TODO"
          name="todoNombre"
          value={todo.todoNombre}
          onChange={(e) => setTodo({ ...todo, todoNombre: e.target.value })}
        />
        <textarea
          className="form-control mb-2"
          type="text"
          placeholder="Ingrese un TODO"
          name="todoDescripcion"
          value={todo.todoDescripcion}
          onChange={(e) =>
            setTodo({ ...todo, todoDescripcion: e.target.value })
          }
        />
        <select
          className="form-control mb-2"
          name="todoEstado"
          value={todo.todoEstado}
          onChange={(e) => setTodo({ ...todo, todoEstado: e.target.value })}
        >
          <option value="pendiente">Pendiente</option>
          <option value="completado">Completado</option>
        </select>
        <button
          className="btn btn-primary"
          type="submit"
        >
          Agregar
        </button>
      </form>
    </div>
  );
};

export default Form;

Ahora si se ve más bonito:

jsx
import { useState } from "react";

const Form = () => {
  const [todo, setTodo] = useState({
    todoNombre: "",
    todoDescripcion: "",
    todoEstado: "pendiente",
  });

  const handleSubmit = (e) => {
    e.preventDefault();

    console.log(todo);
  };

  const handleChange = (e) => {
    // console.log(e.target.name);
    // console.log(e.target.value);
    // setTodo({ ...todo, [e.target.name]: e.target.value });
    // utilizando el callback
    setTodo((prev) => ({
      ...prev,
      [e.target.name]: e.target.value,
    }));
  };

  // console.log(todo);

  return (
    <div className="container mt-2">
      <form onSubmit={handleSubmit}>
        <input
          className="form-control mb-2"
          type="text"
          placeholder="Ingrese un TODO"
          name="todoNombre"
          value={todo.todoNombre}
          onChange={handleChange}
        />
        <textarea
          className="form-control mb-2"
          type="text"
          placeholder="Ingrese un TODO"
          name="todoDescripcion"
          value={todo.todoDescripcion}
          onChange={handleChange}
        />
        <select
          className="form-control mb-2"
          name="todoEstado"
          value={todo.todoEstado}
          onChange={handleChange}
        >
          <option value="pendiente">Pendiente</option>
          <option value="completado">Completado</option>
        </select>
        <button
          className="btn btn-primary"
          type="submit"
        >
          Agregar
        </button>
      </form>
    </div>
  );
};

export default Form;

Checkbox

jsx
<div className="form-check">
    <input
        className="form-check-input"
        type="checkbox"
        id="flexCheckDefault"
        checked={todo.todoCheck}
        onChange={handleChange}
        name="todoCheck"
    />
    <label className="form-check-label" htmlFor="flexCheckDefault">
        Dar prioridad
    </label>
</div>
jsx
const [todo, setTodo] = useState({
  todoNombre: "",
  todoDescripcion: "",
  todoEstado: "pendiente",
  todoCheck: false,
});

const handleChange = (e) => {
  const { name, value, checked, type } = e.target;

  // utilizando el callback
  setTodo((prev) => ({
    ...prev,
    [name]: type === "checkbox" ? checked : value,
  }));
};

console.log(todo);

Pequeña validación

jsx
const [error, setError] = useState(false);

const handleSubmit = (e) => {
  e.preventDefault();

  const { todoNombre, todoDescripcion } = todo;

  // pequeña validación
  if (!todoNombre.trim() || !todoDescripcion.trim()) {
    console.log("campos vacíos");
    setError(true);
    return;
  } else {
    setError(false);
  }

  // Enviar todo a un array!
};

const PintarError = () => (
  <div className="alert alert-danger">Todos los campos obligatorios</div>
);
jsx
<div className="container mt-2">
  <h2>Formulario</h2>
  {error && <PintarError />}
  ...
</div>

Todo junto:

jsx
import { useState } from "react";

const Form = () => {
  const [todo, setTodo] = useState({
    todoNombre: "",
    todoDescripcion: "",
    todoEstado: "pendiente",
    todoCheck: false,
  });

  const [error, setError] = useState(false);

  const handleChange = (e) => {
    const { name, value, checked, type } = e.target;

    // utilizando el callback
    setTodo((prev) => ({
      ...prev,
      [name]: type === "checkbox" ? checked : value,
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    const { todoNombre, todoDescripcion } = todo;

    // pequeña validación
    if (!todoNombre.trim() || !todoDescripcion.trim()) {
      console.log("campos vacíos");
      setError(true);
      return;
    } else {
      setError(false);
    }

    // Enviar todo a un array!
  };

  const PintarError = () => (
    <div className="alert alert-danger">Todos los campos obligatorios</div>
  );

  return (
    <div className="container mt-2">
      <h2>Formulario</h2>
      {error && <PintarError />}
      <form onSubmit={handleSubmit}>
        <input
          className="form-control mb-2"
          type="text"
          placeholder="Ingrese un TODO"
          name="todoNombre"
          value={todo.todoNombre}
          onChange={handleChange}
        />
        <textarea
          className="form-control mb-2"
          type="text"
          placeholder="Ingrese un TODO"
          name="todoDescripcion"
          value={todo.todoDescripcion}
          onChange={handleChange}
        />
        <select
          className="form-control mb-2"
          name="todoEstado"
          value={todo.todoEstado}
          onChange={handleChange}
        >
          <option value="pendiente">Pendiente</option>
          <option value="completado">Completado</option>
        </select>
        <div className="form-check">
          <input
            className="form-check-input"
            type="checkbox"
            id="flexCheckDefault"
            checked={todo.todoCheck}
            onChange={handleChange}
            name="todoCheck"
          />
          <label
            className="form-check-label"
            htmlFor="flexCheckDefault"
          >
            Dar prioridad
          </label>
        </div>
        <button
          className="btn btn-primary"
          type="submit"
        >
          Agregar
        </button>
      </form>
    </div>
  );
};

export default Form;

Validación

Si lo que estás buscando es una solución completa incluyendo validación, tener en cuenta los campos visitados y manejar el envío del formulario, Formik es una de las opciones populares.

También existen otras como: react hook form la cual veremos a continuación.

Código final github