Formularios en React Parte I

Formularios en React Parte I

Los elementos de formularios HTML conservan naturalmente algún estado interno por lo que en React/JSX se manejan de manera diferente.

Introducción

En el pasado, para manejar un formulario,lo creariamos en nuestro documento HTML y le asignaríamos una propiedad action = "https://my-php-backend.com" y method ="POST", generalmente se trataba de una url a un servidor PHP el cual procesaría nuestro formulario una vez enviado:

<html>
    <head>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>

        <form 
            action="https://my-php-backend.com" 
            method="POST"
            id="my-form"
        >
            <label for="first-name">First Name: </label>
            <input 
                type="text" 
                id="first-name" 
                name="firstName" 
                class="input" 
            />
            <br />
            <label for="last-name">Last Name: </label>
            <input 
                type="text" 
                id="last-name" 
                name="lastName" 
                class="input" 
            />
            <br />
            <input type="submit" value="Submit" />
        </form>

        <script src="index.pack.js"></script>
    </body>
</html>

Con la aparición de JavaScript el mismo proceso sería de esta forma:

document.getElementById("my-form").addEventListener("submit", function(event) {
    event.preventDefault()
    const formElements = event.target.elements
    const {firstName, lastName} = formElements
    submitViaAPI({
        firstName: firstName.value,
        lastName: lastName.value
    })
})

function submitViaAPI(data) {
    console.log(data)
    console.log("Submitted!")
}

Se selecciona el formulario, se le añade un manejador de eventos y, la función que se ejecuta cuando el formulario es enviado, reúne toda la información y la redirige a una API, de alguna manera.

El punto aqui es que cuando le hacemos submit al formulario, reúne toda la información inmediatamente y luego la envia.

En React, en vez de esperar a que el formulario sea enviado y luego reunir la información, creamos un estado y, a cada tecla presionada, click en un checkbox etc.. actualizamos el estado, por lo que siempre estamos "escuchando" a los cambios en los inputs del formulario y, cuando finalmente se envía el formulario, no tenemos que reunir ninguna información, eso ya está hecho a través de la actualización dinámica del estado.

Requisitos

Escuchando a los cambios de input

Veamos un ejemplo simple de cómo manejar formularios en React. Como hemos dicho, en Vanilla JavaScript tendríamos un botón para hacer submit del formulario, y cuando le hacemos clic, se ejecuta una función la cual reúne todos los datos del formulario y luego toma una acción con esos datos.

En lugar de eso, lo que vamos a hacer en React es víncular el valor de los inputs a un estado, por lo que se actualiza inmediatamente.

import React from "react"

export default function Form() {
    const [nombre, setNombre] = React.useState("")

    console.log(nombre)

    function handleChange(evento) {
        const valorInput = evento.target.value;
        setNombre(valorInput)
    }

    return (
        <form>
            <input
                type="text"
                placeholder="First Name"
                onChange={handleChange}
            />
        </form>
    )
}

Como podemos observar, al usar el atributo onChange y como consecuencia ejecutar handleChange, esta función recibe un objeto de evento, podemos acceder a sus propiedades como .value.

Usamos este valor para actualizar nuestro estado.

inputform.png

Supongamos que ahora no solo queremos guardar el nombre como también los apellidos. En un principio podriamos pensar en agregar un estado para los apellidos y otro manejador de eventos:

import React from "react"

export default function Form() {
    const [nombre, setNombre] = React.useState("")
    const [apellidos, setApellidos] = React.useState("")


    console.log(nombre, apellidos)

    function handleNombreChange(event) {
        setNombre(event.target.value)
    }

    function handleApellidosChange(event) {
        setApellidos(event.target.value)
    }

    return (
        <form>
            <input
                type="text"
                placeholder="Nombre"
                onChange={handleNombreChange}
            />
            <input
                type="text"
                placeholder="Apellidos"
                onChange={handleApellidosChange}
            />
        </form>
    )
}

inputform2.png

Esto naturalmente funcionaría, aunque imaginemos que nuestro formulario tendrá 20 inputs, tendríamos que crear una función manejadora de eventos y un estado para cada uno de ellos, por lo que se volvería insostenible, nada escalable e impráctico.

Para lidiar con esto de la mejor forma, podemos usar solamente un estado, el cual su variable de estado es un objeto, y usamos el objeto de evento que recibimos en nuestro manejador de eventos, para determinar qué propiedades de dicho objeto se están actualizando.

Uno de los beneficios de este acercamiento es que disponemos de una sola función de estado que usaremos para acualizar el objeto. Para identificar de qué input viene qué información, debemos dotarlos de una propiedad "name" la cual vamos a filtrar en nuestro manejador de eventos con evento.target.name, además de evento.target.value para su valor.

import React from "react"

export default function Form() {
    const [formData, setFormData] = React.useState(
        {nombre: "", apellidos: ""}
    )

    console.log(formData)

    function handleChange(eventObject) {
        const nombreInput = eventObject.target.name;
        const valorInput = eventObject.target.value;
        setFormData(estadoAnterior => {
            return {
                ...estadoAnterior,
                [nombreInput]: valorInput
            }
        })
    }

    return (
        <form>
            <input
                type="text"
                placeholder="First Name"
                onChange={handleChange}
                name="nombre"
            />
            <input
                type="text"
                placeholder="Last Name"
                onChange={handleChange}
                name="apellidos"
            />
        </form>
    )
}

inputform3.png

Inputs no controlados vs inputs controlados

Input no controlado

Un input no controlado es un input HTML tradicional,estos mantienen su propio estado interno basado en lo que introdujo el usuario y, la información del formulario es manejada por el DOM, por lo que tenemos que apuntar a la instancia del elemento del formulario para rescatar sus valores del DOM.

¿Qué significa esto?

Significa que cada elemento del formulario contiene un valor. Su valor puede ser escrito (<input>,<textarea>) o seleccionado (<checkbox>,<select>,<radiobutton> etc...) por el usuario o el navegador.Cuando el valor del elemento cambia (al escribir o seleccionar), es actualizado.

Podemos manipular el valor de uno de estos elementos usando la propiedad .value en su instancia de HTMLElement.

Input controlado

En un input controlado, la información del formulario se maneja através de un estado dentro del componente.

Dicho estado sirve como "fuente única de verdad" para los elementos input que son renderizados por el componente,de esta forma, no podrá cambiar su valor a menos que cambie el estado. Se unifica el valor del estado con la del input.

DATA FLOW.jpg

Usando inputs controlados

Ahora mismo, en nuestra aplicación, tenemos "dos fuentes de verdad",una la maneja nuestro input en si y,la otra se está actualizando cada vez que presionamos una tecla que se almacena en nuestro estado de React.

Por supuesto, nuestro valor del input se está viendo reflejado en nuestro estado en React. Pero la forma adecuada de lidiar con esto es que hagamos que nuestro estado de React administre la información al estado (value) del input.

Veamos como quedaria:

import React from "react"

export default function Form() {
    const [formData, setFormData] = React.useState(
        {nombre: "", apellidos: ""}
    )


    console.log(formData)
    function handleChange(eventObject) {
        const nombreInput = eventObject.target.name;
        const valorInput = eventObject.target.value;
        setFormData(estadoAnterior => {
            return {
                ...estadoAnterior,
                [nombreInput]: valorInput
            }
        })
    }

    return (
        <form>
            <input
                type="text"
                placeholder="Nombre"
                onChange={handleChange}
                name="nombre"
                value={formData.nombre}
            />
            <input
                type="text"
                placeholder="Apellidos"
                onChange={handleChange}
                name="apellidos"
                value={formData.apellidos}
            />
        </form>
    )
}

Como vemos, añadimos la propiedad value a nuestros inputs y su valor seria el valor de su propiedad correspondiente dentro de nuestro estado de React. (formData.nombre,formData.apellidos).

Visualmente no se ve ninguna diferencia, pero conceptualmente, cuando escribimos algo en nuestro campo del input, el valor de éste ya no está siendo controlado por el input en si, pero por React.

Cada cambio que hacemos, ejecuta nuestra función handleChange, la cual actualiza la propiedad correspondiente de nuestro objeto de estado, que a su vez re-renderiza nuestro formulario y esto hace que el valor del input sea correspondiente con el valor del estado.

Por lo que ahora el estado es el responsable de decirle al input qué debe mostrar, en lugar del input decirle al estado lo que debe guardar.

react form.png

En la parte II miraremos cómo manejar la información de inputs de tipo textarea,checkbox, radiobutton y select/option