# Autoguardado de formulario con React y Firebase

## Introducción

Si alguna vez has utilizado un CMS habrás visto como a medida que escribes tu contenido, se va guardando automáticamente. Esta funcionalidad vamos a recrear en este tutorial, utilizando React como front-end y Firebase como back-end.

## Proyecto

El proyecto consiste en un formulario con dos campos, título y contenido. A medida que llenemos los campos, de forma automática se disparará una función que se encargará de guardar el contenido en una base de datos en Firebase. Una barra de estado nos indicará el status del proceso de guardado.

## Consideraciones

El tutorial es enteramente práctico, por lo que no cubriremos conceptos teóricos y los aspectos básicos de las herramientas que vamos a utilizar. Por ello es recomendable tener conocimiento del lenguaje de programación javascript, nociones básicas de React y alguna experiencia en la utilización de Firebase.

## Instalación y configuración

Comencemos creando la aplicación React. Lo llamé "react-autosave".

```plaintext
npx create-react-app react-autosave
```

Cuando finalice la instalación entra al directorio del código fuente y crea una carpeta para el fichero de configuración de firebase, `react-autosave/src/firebase/firebase-config.js` .

Ahora ve a la consola de firebase y crea un nuevo proyecto. Haz clic en "Agregar proyecto" y dale un nombre. Yo lo llamé "react-firebase-autosave". Cuando finalice la creación haz clic en Cloud Firebase y crea un nueva base de datos. Activa el modo prueba.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672172258123/e76b7dc4-8718-4d1a-9f4d-4f71144da464.png align="center")

Luego te pidirá habilitar la ubicación donde se almacenará los datos de Cloud Firestore. Dale habilitar. Accederás a tu panel de control. Ahora hay que registrar tu app para el proyecto. Haz clic en el ícono de configuración y elige "Configuración de proyecto".

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672170692594/bfbd73f9-2a7a-4911-b90e-3d97958d2e30.png align="center")

Ve a la sección "Tus apps", aquí debemos registrar la app para nuestra web.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672170837831/3c1111f0-bf0b-4f1d-b171-04f06cb865a1.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672170888937/0e944c68-4d2b-464a-8765-f250745e7993.png align="center")

Una vez que registres el nombre de la app tendrás acceso a la configuración del SDK de firebase para tu proyecto.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672171611391/0502b1e6-4b76-44ce-a3b4-382f77b2a46a.png align="center")

Esta configuración es la que debemos pegar en el fichero de configuración de firebase que creamos anteriormente, `react-autosave/src/firebase/firebase-config.js` . Primero instalamos el SDK de firebase con `npm i firebase` .

```javascript
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";

const firebaseConfig = {
  apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  authDomain: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  projectId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  storageBucket: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  messagingSenderId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  appId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
};

const app = initializeApp(firebaseConfig);
const db = getFirestore(app);

export { db };
```

Además de inicializar la app, accedemos a la base de datos de Cloud Firestore y lo guardamos en `const db` , luego lo exportamos. Ya tenemos todo listo para comenzar a trabajar con Cloud Firestore. Ahora vamos a crear el formulario.

## Formulario

Para los estilos del proyecto vamos a utilizar TailwindCSS. La documentación para la instalación y configuración de TailwindCSS en React podés encontrarla [AQUÍ](https://tailwindcss.com/docs/guides/create-react-app).

Dentro del directorio fuente crea una carpeta para el componente del formulario, `react-autosave/src/Form/Form.js` .

```javascript
function Form() {
    return (
        <>
            <section className="text-white w-[600px] p-4">
                <header className="text-white text-4xl text-center py-4">Form Autosave</header>
                <form className="mt-5">
                    <div>
                        <label className="w-full">Title:</label>
                        <input type="text" name="title" placeholder="Title" className="w-full p-2 rounded bg-gray-700 my-2"/>
                    </div>
                    <div>
                        <label className="w-full">Content:</label>
                        <textarea name="content" rows="10" cols="30" placeholder="Content" className="w-full p-2 rounded bg-gray-700 my-2"/>
                    </div>
                </form>
            </section>
        </>
    )
}

 export default Form;
```

Modificamos el fichero `react-autosave/src/App.js` para incorporar el formulario.

```javascript
import Form from "./Form/Form";

function App() {
  return (
    <div className="min-h-screen max-w-full bg-slate-900 flex justify-center items-center">
      <Form />
    </div>
  );
}

export default App;
```

## Estado del Formulario

Usaremos `useState` para controlar el estado del formulario y vamos a crear una función `handleChange` para actualizar los valores.

```javascript
import { useState } from "react";

function Form() {
    const [post, setPost] = useState({
        id: '',
        title: '',
        content: ''
    });
    const handleChange = (name, value) => {
        setPost({...post, [name]: value});
    }
    return (
        <>
            <section className="text-white w-[600px] p-4">
                <header className="text-white text-4xl text-center py-4">Form Autosave</header>
                <form className="mt-5">
                    <div>
                        <label className="w-full">Title:</label>
                        <input type="text" name="title" placeholder="Title" className="w-full p-2 rounded bg-gray-700 my-2"
                        onChange={(e) => handleChange(e.target.name, e.target.value)}
                        />
                    </div>
                    <div>
                        <label className="w-full">Content:</label>
                        <textarea name="content" rows="10" cols="30" placeholder="Content" className="w-full p-2 rounded bg-gray-700 my-2"
                        onChange={(e) => handleChange(e.target.name, e.target.value)}
                        />
                    </div>
                </form>
            </section>
        </>
    )
}

 export default Form;
```

Con esta función podemos actualizar el estado haciendo uso del evento `onChange` . Sin embargo, esto se ejecuta cada vez que tipeamos en el formulario, compromentiendo el rendimiento de la app. Podemos retrasar la llamada a la función `handleChange` con el método `debounce` . Para ello utilizaremos la librería [radash](https://radash-docs.vercel.app/docs/getting-started). Para instalarlo ejecutamos el comando `npm i radash` . Ahora modificamos la función `handleChange` como sigue.

```javascript
//Importamos la función debounce de radash
import { debounce } from "radash";
....
//Modificamos la función hanbleChange
const handleChange = debounce({ delay: 700 }, (name, value) => {
       setPost({...post, [name]: value});
    });
```

El método `debounce` recibe un objeto con la opción `{ delay: 700 }` , el cual indica los milisegundos de retraso para llamar a la función. Como segundo parámetro recibe la función que queremos ejecutar una vez pasado el "delay". Ahora la función `handleChange` solo se ejecutará cuando dejemos de tipear.

Ahora debemos darle un `id` al objeto `post` que vamos a guardar. Lo haremos con la librería `uuid` . Lo instalamos con el comando `npm i uuid` .

La propiedad `id` del objeto solo se debe establecer en la primera llamada, de lo contrario estaría generando uno nuevo en cada llamada. Lo hacemos como sigue.

```javascript
//Importamos la función para generar el id
import { v4 as uuidv4 } from "uuid";

//Modificamos la función handleChange
const handleChange = debounce({ delay: 700 }, (name, value) => {
        if(!post.id){
            setPost({...post, id: uuidv4(), [name]: value });
        } else {
            setPost({...post, [name]: value });
        }
    });
```

Si `post.id` está vacío, entoces le asigna un valor, de lo contrario solo actualiza los demás valores, manteniendo el `id` . Este bloque podemos reducirlo haciendo uso del operador condicional ternario como sigue.

```javascript
const handleChange = debounce({ delay: 700 }, (name, value) => {
        !post.id ? setPost({...post, id: uuidv4(), [name]: value}) :                     setPost({...post, [name]: value});
    });
```

Ahora debemos incorporar la función para guardar los datos en nuestra base de datos de Cloud Firestore.

## Cloud Firestore

Para añadir la funcionalidad de guardado vamos a utilizar el "hook" `useEffect` . Lo añadimos como sigue.

```javascript
// Importamos el hook useEffect
import { useEffect, useState } from "react";

// Importamos la instancia de la base de datos Cloud Firestore
import { db } from "../firebase/firebase-config";
// Importamos las funciones para establecer un doc nuevo
import { doc, setDoc } from "firebase/firestore";
...
useEffect(() => {
        if (post.id) {
            setDoc(doc(db, "posts", post.id), { title: post.title, content: post.content});
        }
    }, [post]);
```

Este "hook" se ejecutará cuando se detecte un cambio en el estado de `post` . Con `setDoc` podemos escribir un documento proporcionando una referencia hacia dicho documento. Si el documento existe, lo actualizará, si no, lo creará. Con `doc` especificamos dicha referencia. Recibe la instancia de la base de datos, el nombre de la colección ("posts") y un identificador de documento, en nuestro caso usamos `post.id` .

Como el estado inicial de `post.id` es una cadena vacía, la función `doc` disparará un error. Para evitarlo establecemos la condición `if` para que la función solo corra cuando `post.id` no esté vacío.

Con esto la funcionalidad está terminada, pero vamos a añadir la barra de estado que nos indicará el progreso del proceso.

## Barra de Estado

Para la barra vamos a usar una librería de íconos animados para React llamado [UseAnimations](https://react.useanimations.com/). Para instalarlo corremos el comando `npm i react-useanimations` .

Dentro del directorio fuente crea una carpeta para la barra de estado `react-autosave/src/statusbar/StatusBar.js` .

```javascript
import UseAnimations from "react-useanimations";
import loading from "react-useanimations/lib/loading";
import checkbox from "react-useanimations/lib/checkBox";

function StatusBar(props) {
    return (
        <>
            {props.sending &&
            <div className="flex justify-center items-center">
                <span className="text-green-700">Sending...</span><UseAnimations animation={loading} size={36} strokeColor={"#15803d"} />
            </div>
            }
            {!props.sending &&
            <div className="flex justify-center items-center gap-1">
                <span className="text-green-700">Saved</span><UseAnimations reverse={true} animation={checkbox} size={36} strokeColor={"#15803d"} autoplay={true} />
            </div>
            }
        </>
    )
}

export default StatusBar;
```

En primer lugar importamos el componente `UseAnimations` . Seguido importamos dos animaciones, `loading` para cuando los datos son enviados, y `checkbox` para cuando los datos han sido guardados.

Dentro del componente `StatusBar` utilizamos un renderizado condicional. Si `props.sending` es verdadero, entonces renderiza la animación `loading` , de lo contrario renderizará la animación `checkbox` .

Volviendo al componente del formulario, importamos la barra de estado. También vamos a establecer un nuevo estado `sending` para determinar si los datos están siendo enviados.

```javascript
// Importamos la barra de estado
import StatusBar from "../statusbar/StatusBar";

// Creamos un estado para el status del envío de datos
const [status, setStatus] = useState({ sending: false });

// Modificamos la función para incorporar el estado del envío de datos
useEffect(() => {
        if (post.id) {
            setStatus({sending: true});
            setDoc(doc(db, "posts", post.id), { title: post.title, content: post.content})
                .then(() => {
                    setStatus({sending: false});
                });
        }
    }, [post]);
```

Antes del envío de datos actualizamos el `status` a verdadero. Cuando el envío de datos finaliza el `status` es actualizado a falso.

Ahora incorporamos la barra al render.

```javascript
<div className="py-2 flex justify-between items-center gap-1 border-y-2 border-green-700">
   <span className="text-green-700">STATUS:</span> { post.id &&
                        <StatusBar sending={status.sending}/>
                    }
</div>
```

El componente `StatusBar` solo se renderiza si `post.id` no está vacío. Además en los `props` del componente pasamos el `status` .

El código completo del componente `Form.js` .

```javascript
import { useEffect, useState } from "react";
import { debounce } from "radash";
import { v4 as uuidv4 } from "uuid";
import { db } from "../firebase/firebase-config";
import { doc, setDoc } from "firebase/firestore";
import StatusBar from "../statusbar/StatusBar";

function Form() {
    const [post, setPost] = useState({
        id: '',
        title: '',
        content: ''
    });

    const [status, setStatus] = useState({ sending: false });

    const handleChange = debounce({ delay: 700 }, (name, value) => {
        !post.id ? setPost({...post, id: uuidv4(), [name]: value}) : setPost({...post, [name]: value});
    });
    
    useEffect(() => {
        if (post.id) {
            setStatus({sending: true});
            setDoc(doc(db, "posts", post.id), { title: post.title, content: post.content})
                .then(() => {
                    setStatus({sending: false});
                });
        }
    }, [post]);

    return (
        <>
            <section className="text-white w-[600px] p-4">
                <header className="text-white text-4xl text-center py-4">Form Autosave</header>
                <div className="py-2 flex justify-between items-center gap-1 border-y-2 border-green-700">
                    <span className="text-green-700">STATUS:</span> { post.id &&
                        <StatusBar sending={ status.sending }/>
                    }
                        
                </div>
                <form className="mt-5">
                    <div>
                        <label className="w-full">Title:</label>
                        <input type="text" name="title" placeholder="Title" className="w-full p-2 rounded bg-gray-700 my-2"
                        onChange={(e) => handleChange(e.target.name, e.target.value)}
                        />
                    </div>
                    <div>
                        <label className="w-full">Content:</label>
                        <textarea name="content" rows="10" cols="30" placeholder="Content" className="w-full p-2 rounded bg-gray-700 my-2"
                        onChange={(e) => handleChange(e.target.name, e.target.value)}
                        />
                    </div>
                </form>
            </section>
        </>
    )
}

 export default Form;
```

Ahora probemos la app. Corre el comando `npm start` .

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672349005472/55d70196-de0c-45d8-bc2c-943ee0d188fb.png align="center")

Inicialmente la barra de estado está vacío.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672349115427/78bf2a5c-afc0-49d5-9805-9bc14cb01c06.png align="center")

Cuando tipeamos el título del post podemos ver como se activa la barra de estado.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672349231363/cc354772-7a05-45d9-b5e3-ed3c8582dbbd.png align="center")

Cuando finaliza el proceso de guardado de datos en la base de datos de Cloud Firestore, la barra de estado se actualiza.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1672349360920/2c0f8c23-6763-4272-adee-060f33f875d3.png align="center")

Podemos verificar como los datos han sido guardados en la base de datos correctamente.

Finalizamos con el tutorial.

## Conclusión

Hemos aprendido a integrar React con Cloud Firestore de Firebase. Como ejecutar una función de forma automática para guardar los datos y como incorporar una barra de estado que nos indica el progreso de guardado.

Puedes encontrar el proyecto completo en mi repositorio de GitHub haciendo clic [AQUÍ](https://github.com/AlegreCode/react-autosave).

Espero que lo hayas encontrado entretenido, instructivo y claro. Si tienes alguna duda, puedes hacérmelo saber en los comentarios. Pronto estaré subiendo más tutoriales.

Nos vemos en la próxima. Saludos!👋😊
