Cómo usar el almacenamiento local para conservar los datos del formulario en JavaScript

Cómo utilizar el almacenamiento local para mejorar la experiencia del usuario mediante la copia de seguridad y la restauración de datos de formulario para los usuarios.

Primeros pasos

Para este tutorial, vamos a utilizar CheatCode Next.js Boilerplate como punto de partida para nuestro trabajo. Para comenzar, clonemos una copia:

Terminal

git clone https://github.com/cheatcode/nextjs-boilerplate.git

A continuación, cd en el proyecto e instalar las dependencias:

Terminal

cd nextjs-boilerplate && npm install

Finalmente, inicie el servidor de desarrollo:

Terminal

npm run dev

Con eso, estamos listos para comenzar.

Construyendo un formulario

Antes de comenzar a conservar los datos del formulario, necesitamos un formulario del que podamos extraer datos y volver a cargarlos. Para comenzar, agregaremos un nuevo componente de página a través de React para albergar nuestro formulario:

/páginas/index.js

import React from "react";
import StyledIndex from "./index.css";

class Index extends React.Component {
  state = {};

  render() {
    return (
      <StyledIndex>
        <form>
          // We'll render our form fields here...
        </form>
      </StyledIndex>
    );
  }
}

export default Index;

En una aplicación Next.js, todos los archivos y carpetas bajo el /pages Carpeta doble como rutas o URL en el navegador. Aquí, creando nuestra página en /pages/index.js , en el navegador, podemos esperar acceder a nuestra página en http://localhost:5000/ (el índice o raíz de nuestra aplicación).

Para nuestro componente, estamos utilizando el enfoque basado en clases en React en lugar del enfoque basado en funciones (nos beneficiaremos de esto más adelante cuando carguemos datos en nuestro formulario desde el almacenamiento local). Aquí, dentro del render() método, estamos representando un componente con estilo <StyledIndex /> que usaremos para aplicar un estilo básico a nuestro <form></form> . Echemos un vistazo a ese archivo ahora:

/páginas/index.css.js

import styled from "styled-components";

export default styled.div`
  form {
    max-width: 50%;
  }
`;

styled-components es una biblioteca que ayuda a agregar fácilmente CSS a nuestros componentes de React. Funciona generando componentes de React que contienen automáticamente algún elemento HTML y luego adjuntando los estilos que proporcionamos (aquí, lo que hay entre los acentos graves) a ese elemento. Arriba, importamos styled del styled-components paquete (instalado automáticamente en el repetitivo que clonamos anteriormente) y luego cree un nuevo componente con estilo que contenga un HTML <div></div> elemento.

Aunque no lo parezca, aquí, styled.div es técnicamente una función styled.div() . La sintaxis aquí es una característica de conveniencia en JavaScript que nos permite llamar a una función solo esperando un solo argumento en el tipo de una cadena al soltar los paréntesis y usar acentos graves alrededor de la cadena que se pasa. Esa cadena aquí contiene nuestro CSS que limita el ancho de nuestro formulario a solo el 50% de la página.

/páginas/index.js

import React from "react";
import StyledIndex from "./index.css";

class Index extends React.Component {
  state = {};

  render() {
    return (
      <StyledIndex>
        <form>
          // We'll render our form fields here...
        </form>
      </StyledIndex>
    );
  }
}

export default Index;

De vuelta en nuestro componente, importamos y renderizamos nuestro componente con estilo, en este caso envolviéndolo alrededor de un HTML <form></form> etiqueta donde mostraremos nuestros campos de formulario.

/páginas/index.js

import React from "react";
import StyledIndex from "./index.css";

class Index extends React.Component {
  state = {};

  render() {
    return (
      <StyledIndex>
        <form>
          <div className="row">
            <div className="col-sm-6">
              <div className="mb-3">
                <label className="form-label">First Name</label>
                <input
                  type="text"
                  name="firstName"
                  className="form-control"
                />
              </div>
            </div>
            <div className="col-sm-6">
              <div className="mb-3">
                <label className="form-label">Last Name</label>
                <input
                  type="text"
                  name="lastName"
                  className="form-control"
                />
              </div>
            </div>
          </div>
          <div className="row">
            <div className="col-sm-12">
              <div className="mb-3">
                <label className="form-label">Favorite Ice Cream Flavor</label>
                <select
                  className="form-select"
                >
                  <option value="chocolate">Chocolate</option>
                  <option value="vanilla">Vanilla</option>
                  <option value="strawberry">Strawberry</option>
                  <option value="neopolitan">Neopolitan</option>
                </select>
              </div>
            </div>
          </div>
          <div className="row">
            <div className="col-sm-12">
              <div className="mb-5">
                <label className="form-label">Toppings</label>
                <div class="form-check">
                  <input
                    className="form-check-input"
                    type="checkbox"
                    value="sprinkles"
                  />
                  <label className="form-check-label">Sprinkles</label>
                </div>
                <div className="form-check">
                  <input
                    className="form-check-input"
                    type="checkbox"
                    value="cherry"
                  />
                  <label className="form-check-label">Cherry</label>
                </div>
                <div className="form-check">
                  <input
                    className="form-check-input"
                    type="checkbox"
                    value="hotFudge"
                  />
                  <label className="form-check-label">Hot Fudge</label>
                </div>
              </div>
            </div>
          </div>
          <button className="btn btn-primary" style={{ marginRight: "10px" }}>
            Submit
          </button>
          <button
            className="btn btn-light"
            type="button"
          >
            Reset Form
          </button>
        </form>
      </StyledIndex>
    );
  }
}

export default Index;

Completando el cuerpo de nuestro formulario, aquí, hemos agregado una combinación de entradas HTML para demostrar cómo recuperar datos de un formulario y luego volver a configurarlos después de actualizar la página desde el almacenamiento local. Tenemos seis campos:

  1. Una entrada de texto de nombre
  2. Una entrada de texto de apellido
  3. Una entrada selecta para seleccionar su sabor de helado favorito
  4. Una serie de casillas de verificación para marcar coberturas de helado

Si bien nuestro formulario aparecerá en la pantalla y se podrá completar, si actualizamos la página, se perderán todos los datos que ingresemos en el formulario. A continuación, para evitar esto, vamos a aprender cómo almacenar nuestros datos en el estado de nuestro componente React primero y luego hacer una copia de seguridad en el almacenamiento local.

Configuración de datos en almacenamiento estatal y local

Arriba, configuramos un componente de página que representa nuestros campos de formulario. Ahora, queremos capturar el valor de las entradas en ese formulario y establecerlas en el estado de nuestro componente, así como en el almacenamiento local. Para hacerlo, vamos a agregar una función que podemos llamar desde todas nuestras entradas que centralizará la configuración de los valores de entrada en el estado y almacenamiento local.

Terminal

import React from "react";
import StyledIndex from "./index.css";

class Index extends React.Component {
  state = {};

  handleUpdateState = (field = "", value = "") => {
    this.setState({ [field]: value }, () => {
      if (localStorage) {
        localStorage.setItem("formData", JSON.stringify(this.state));
      }
    });
  };

  render() {
    const { firstName, lastName, iceCreamFlavor, sprinkles, cherry, hotFudge } =
      this.state;

    return (
      <StyledIndex>
        <form>
          <div className="row">
            <div className="col-sm-6">
              <div className="mb-3">
                <label className="form-label">First Name</label>
                <input
                  type="text"
                  name="firstName"
                  value={firstName}
                  onChange={(event) =>
                    this.handleUpdateState("firstName", event.target.value)
                  }
                  className="form-control"
                />
              </div>
            </div>
            <div className="col-sm-6">
              <div className="mb-3">
                <label className="form-label">Last Name</label>
                <input
                  type="text"
                  name="lastName"
                  value={lastName}
                  onChange={(event) =>
                    this.handleUpdateState("lastName", event.target.value)
                  }
                  className="form-control"
                />
              </div>
            </div>
          </div>
          <div className="row">
            <div className="col-sm-12">
              <div className="mb-3">
                <label className="form-label">Favorite Ice Cream Flavor</label>
                <select
                  className="form-select"
                  value={iceCreamFlavor}
                  onChange={(event) =>
                    this.handleUpdateState("iceCreamFlavor", event.target.value)
                  }
                >
                  <option value="chocolate">Chocolate</option>
                  <option value="vanilla">Vanilla</option>
                  <option value="strawberry">Strawberry</option>
                  <option value="neopolitan">Neopolitan</option>
                </select>
              </div>
            </div>
          </div>
          <div className="row">
            <div className="col-sm-12">
              <div className="mb-5">
                <label className="form-label">Toppings</label>
                <div class="form-check">
                  <input
                    className="form-check-input"
                    type="checkbox"
                    value="sprinkles"
                    checked={sprinkles}
                    onChange={(event) =>
                      this.handleUpdateState("sprinkles", event.target.checked)
                    }
                  />
                  <label className="form-check-label">Sprinkles</label>
                </div>
                <div className="form-check">
                  <input
                    className="form-check-input"
                    type="checkbox"
                    value="cherry"
                    checked={cherry}
                    onChange={(event) =>
                      this.handleUpdateState("cherry", event.target.checked)
                    }
                  />
                  <label className="form-check-label">Cherry</label>
                </div>
                <div className="form-check">
                  <input
                    className="form-check-input"
                    type="checkbox"
                    value="hotFudge"
                    checked={hotFudge}
                    onChange={(event) =>
                      this.handleUpdateState("hotFudge", event.target.checked)
                    }
                  />
                  <label className="form-check-label">Hot Fudge</label>
                </div>
              </div>
            </div>
          </div>
          <button className="btn btn-primary" style={{ marginRight: "10px" }}>
            Submit
          </button>
          <button
            className="btn btn-light"
            type="button"
          >
            Reset Form
          </button>
        </form>
      </StyledIndex>
    );
  }
}

export default Index;

Aquí, hemos agregado una función a nuestra clase handleUpdateState que acepta dos argumentos:field y value . El primer argumento field es el nombre del campo que queremos establecer en el estado y value es el valor que queremos asignar a ese campo.

Dentro de esa función, llamamos a this.setState() para actualizar el valor de estado de nuestro componente, usando una sintaxis de notación de paréntesis especial para ayudarnos a establecer dinámicamente la propiedad que queremos actualizar en el estado (al establecer valores en el estado, pasamos uno o más pares clave/valor en un objeto). Aquí, [field] será reemplazado por cualquier field cadena que pasamos como primer argumento, por ejemplo { firstName: value } o { iceCreamFlavor: value } .

Como veremos en un momento, esto nos permite llamar a handleUpdateState desde cualquier campo de formulario mientras aseguramos que nuestro comportamiento sea consistente. También dentro de esta función, pasamos una función de devolución de llamada a this.setState() para decirle a React "haga esto después de que haya comprometido con éxito el valor de nuestro campo al estado del componente". En esa función, presentamos nuestro uso de almacenamiento local.

Primero, hacemos un if (localStorage) para asegurarse de que el almacenamiento local esté disponible. Esto es necesario porque es posible que algunos navegadores no admitan el almacenamiento local. Esto incluye los navegadores modernos que se ejecutan en modo privado .

Si localStorage existe, llamamos a su .setItem primero pasando el nombre del valor que queremos almacenar como primer argumento y luego pasando el valor que queremos almacenar como segundo. Aquí, porque localStorage solo admite el almacenamiento de cadenas, usamos JSON.stringify para encadenar la totalidad de nuestro this.state valor. Hacemos esto porque queremos localStorage para ser la representación más actualizada de la entrada de un usuario.

Abajo en nuestro render() método, hemos añadido dos cosas:

  1. Hemos utilizado la desestructuración de JavaScript para "arrancar" nuestros valores de entrada de this.state y he asignado cada valor al value atributo en cada una de nuestras entradas (esto crea lo que se conoce como un componente controlado en React).
  2. Para cada entrada, hemos agregado un onChange función que toma un DOM event y llamadas a this.handleUpdateState() , pasando el nombre del campo y su valor. Para el <input type="checkbox" /> elementos, en lugar de pasar event.target.value pasamos event.target.checked .

Ahora, si comenzamos a escribir en nuestro formulario, deberíamos ver nuestro formData actualizar el valor en el almacenamiento local del navegador:

Ya casi hemos terminado. Para concluir y hacer que esto sea útil, a continuación, aprenderemos cómo cargar los datos que colocamos en el almacenamiento local nuevamente en nuestro formulario después de actualizar la página.

Restaurar un formulario desde el almacenamiento local

Aquí es donde nuestro uso del enfoque de componente React basado en clases vale la pena. Para volver a cargar datos en nuestro formulario, necesitamos saber que el formulario existe en el DOM. Para hacer eso, podemos usar el componentDidMount() función de ciclo de vida en React para informarnos que nuestro formulario está en pantalla y listo para nuestros datos.

/páginas/index.js

import React from "react";
import StyledIndex from "./index.css";

class Index extends React.Component {
  state = {};

  componentDidMount() {
    if (localStorage) {
      const formDataFromLocalStorage = localStorage.getItem("formData");
      if (formDataFromLocalStorage) {
        const formData = JSON.parse(formDataFromLocalStorage);
        this.setState({ ...formData });
      }
    }
  }

  handleUpdateState = (field = "", value = "") => { ... };

  render() {
    const { firstName, lastName, iceCreamFlavor, sprinkles, cherry, hotFudge } =
      this.state;

    return (
      <StyledIndex>
        <form>
          ...
        </form>
      </StyledIndex>
    );
  }
}

export default Index;

Dentro de componentDidMount() , primero verificamos si localStorage está definido y, si lo está, intente recuperar nuestro formData valor de localStorage con el .getItem() método, pasando el nombre de nuestro valor formData como una cadena.

A continuación, si obtenemos un valor, debemos convertir la cadena que almacenamos nuevamente en un objeto JavaScript. Para hacerlo, pasamos formDataFromLocalStorage a JSON.parse() . A continuación, con nuestro formData como objeto, llamamos a this.setState() , pasando un objeto cuyas propiedades se establecen mediante JavaScript ... operador de propagación para "desempaquetar" todas las propiedades en formData sobre el objeto que pasamos a .setState() (esto hace que cada propiedad individual vuelva al estado).

¡Con eso, podemos completar nuestro formulario, actualizar la página y ver que nuestros valores se mantienen!

Terminando

En este tutorial, aprendimos a crear un formulario en React que almacena su contenido en el this.state de un componente. valor y respalda ese valor hasta localStorage . Aprendimos cómo acceder condicionalmente a localStorage para evitar problemas con navegadores no compatibles y cómo recuperar un valor existente de localStorage y aplicarlo de nuevo a this.state cuando se monta el componente.