# ReactJS y FaunaDB

### Introducción

[Fauna DB](https://fauna.com/) es una base de datos distribuida y globalmente accesible que ofrece una solución serverless para el almacenamiento de datos. Combina características de bases de datos documentales y relacionales, diseñada específicamente para el desarrollo de aplicaciones modernas.

En este tutorial, aprenderás los conceptos básicos para integrar la base de datos en tu aplicación ReactJS y realizar operaciones CRUD.

### Proyecto ReactJS

Vamos a utilizar ViteJS para crea el proyecto ReactJS, abre una consola y escribe el siguiente comando:

```bash
npm create vite@latest reactjs-fauna -- --template react
```

Este comando generará un directorio `reactjs-fauna` , entra e instala las dependencias necesarias con el comando `npm install` . Cuando finalice, puedes ejecutar el comando `npm run dev` para correr el proyecto.

### Instalación y configuración de TailwindCSS

Para añadirle estilos a nuestro proyecto, vamos a utilizar el framework [TailwindCSS](https://tailwindcss.com/). Instalamos las dependencias necesarias con el siguiente comando:

```bash
npm install -D tailwindcss postcss autoprefixer
```

Para inicializar TailwindCSS corremos el comando:

```bash
npx tailwindcss init -p
```

Esto creará dos ficheros, `tailwind.config.js` y `postcss.config.js` . Abre el fichero `tailwind.config.js` y actualiza como sigue:

```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
```

Ahora abre el fichero `src/index.css` y añade las directivas de tailwind como sigue:

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

Ahora asegúrate que el archivo CSS esté importado en el archivo principal del proyecto (en este caso `src/main.jsx`)

```javascript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import './index.css' // IMPORTAMOS LOS ESTILOS DE TAILWINDCSS

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)
```

Finalmente vamos a instalar lucide-react que es un biblioteca de íconos para react, basada en el proyecto [Lucide](https://lucide.dev/guide/packages/lucide-react). Ofrece una colección de iconos de código abierto, simples y consistentes que pueden ser fácilmente integrados en aplicaciones React.

Lo instalas con el siguiente comando:

```javascript
npm install lucide-react
```

Ya tenemos todo listo para comenzar a crear los componentes de nuestro proyecto.

### Componente Form

El componente Form.jsx nos permitirá añadir tareas a la lista de tareas. Crea un fichero `src/components/Form.jsx` y añade el siguiente código:

```javascript
import { Plus } from 'lucide-react';

export default function Form({ newTodo, setNewTodo, addTodo }) {
  return (
    <form onSubmit={addTodo} className="flex gap-2 mb-6">
      <input
        type="text"
        value={newTodo}
        onChange={(e) => setNewTodo(e.target.value)}
        placeholder="¿Qué necesitas hacer?"
        className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
      />
      <button
        type="submit"
        className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2"
      >
        <Plus size={20} />
        Agregar
      </button>
    </form>
  );
}
```

Vamos a explicar las funciones más destacadas:

**Componente** `Form`:

Es una función que recibe tres propiedades: `newTodo`, `setNewTodo`, y `addTodo`.

* `newTodo`: Representa el valor actual del texto de la nueva tarea.
    
* `setNewTodo`: Función que se usa para actualizar el valor de `newTodo`.
    
* `addTodo`: Función que se ejecuta cuando el usuario envía el formulario, agregando una nueva tarea a la lista.
    

**Formulario HTML con manejo de eventos**:

```xml
<form onSubmit={addTodo} className="flex gap-2 mb-6">
```

* Se usa un elemento `<form>` que ejecuta la función `addTodo` cuando se envía (evento `onSubmit`). La función `addTodo` se encargará de agregar una nueva tarea a la lista de tareas.
    

**Campo de entrada de texto**:

```xml
<input
  type="text"
  value={newTodo}
  onChange={(e) => setNewTodo(e.target.value)}
  placeholder="¿Qué necesitas hacer?"
  className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
```

* El valor de este campo (`value={newTodo}`) está ligado a la propiedad `newTodo`, permitiendo el control del estado.
    
* El evento `onChange` actualiza el estado (`setNewTodo`) cada vez que el usuario escribe algo, capturando el valor ingresado.
    

Hemos finalizado el componente Form.jsx . Continuamos con el componente Todo.jsx.

### Componente Todo

El componente Todo.jsx tendrá como función mostrar la información de cada una de las tareas de la lista. Crea un fichero `src/components/Todo.jsx` y añade el siguiente código:

```javascript
import { Trash2, Check } from 'lucide-react';

export default function Todo({ todo, toggleTodo, deleteTodo }) {
  return (
    <div className="flex items-center justify-between p-4 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors">
      <div className="flex items-center gap-3">
        <button
          onClick={() => toggleTodo(todo.id)}
          className={`w-6 h-6 rounded-full border-2 flex items-center justify-center
            ${todo.completed 
              ? 'bg-green-500 border-green-500 text-white' 
              : 'border-gray-400'
            }`}
        >
          {todo.completed && <Check size={16} />}
        </button>
        <span className={`text-gray-800 ${todo.completed ? 'line-through text-gray-500' : ''}`}>
          {todo.text}
        </span>
      </div>
      <button
        onClick={() => deleteTodo(todo.id)}
        className="text-red-500 hover:text-red-700 focus:outline-none"
      >
        <Trash2 size={20} />
      </button>
    </div>
  );
}
```

A continuación se explica las funcionalidades más destacadas:

**Propiedades recibidas**:

* `todo`: Un objeto que representa una tarea específica.
    
* `toggleTodo`: Función que se ejecuta cuando el usuario marca o desmarca la tarea como completada.
    
* `deleteTodo`: Función que se ejecuta cuando el usuario elimina la tarea.
    

**Botón para marcar como completada**:

```xml
<button
  onClick={() => toggleTodo(todo.id)}
  className={`w-6 h-6 rounded-full border-2 flex items-center justify-center
    ${todo.completed 
      ? 'bg-green-500 border-green-500 text-white' 
      : 'border-gray-400'
    }`}
>
  {todo.completed && <Check size={16} />}
</button>
```

* Este botón permite al usuario marcar la tarea como completada o pendiente.
    
* `onClick={() => toggleTodo(`[`todo.id`](http://todo.id)`)}`: Al hacer clic, se ejecuta la función `toggleTodo` pasando el `id` de la tarea, lo que permite alternar el estado de completado.
    
* Estilización dinámica:
    
    * Si la tarea está completada (`todo.completed === true`):
        
        * Se aplica fondo y borde verde (`bg-green-500`, `border-green-500`), y el ícono `Check` (una marca de verificación) es visible.
            
    * Si la tarea no está completada:
        
        * Solo se muestra un borde gris (`border-gray-400`), sin ícono dentro del botón.
            

**Texto de la tarea**:

```xml
<span className={`text-gray-800 ${todo.completed ? 'line-through text-gray-500' : ''}`}>
  {todo.text}
</span>
```

* Muestra el texto de la tarea (`todo.text`).
    
* Si la tarea está completada (`todo.completed`), se aplica el estilo de tachado (`line-through`) y el color del texto se vuelve gris claro (`text-gray-500`), indicando visualmente que la tarea ya ha sido realizada. Si no está completada, el texto se mantiene en gris oscuro (`text-gray-800`).
    

**Botón para eliminar la tarea**:

```xml
<button
  onClick={() => deleteTodo(todo.id)}
  className="text-red-500 hover:text-red-700 focus:outline-none"
>
  <Trash2 size={20} />
</button>
```

* Este botón permite eliminar la tarea.
    
* `onClick={() => deleteTodo(`[`todo.id`](http://todo.id)`)}`: Al hacer clic, se ejecuta la función `deleteTodo` con el `id` de la tarea, eliminándola de la lista.
    

Con esto hemos finalizado con el componente Todo.jsx, ahora debemos actualizar el componente principal de la aplicación App.jsx.

### Componente TodoApp

Este componente es la base de la aplicación de lista de tareas. Abre el fichero `src/App.jsx` y actualízalo con el siguiente código:

```javascript
import { useState } from 'react';
import Form from './components/Form';
import Todo from './components/Todo';

export default function TodoApp() {
  const [todos, setTodos] = useState([]);
  const [newTodo, setNewTodo] = useState('');

  const addTodo = (e) => {
    e.preventDefault();
    if (newTodo.trim()) {
      setTodos([...todos, { id: Date.now(), text: newTodo, completed: false }]);
      setNewTodo('');
    }
  };

  const toggleTodo = (id) => {
    setTodos(
      todos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  return (
    <div className="min-h-screen bg-gray-100 py-8">
      <div className="max-w-md mx-auto bg-white rounded-lg shadow-lg p-6">
        <h1 className="text-2xl font-bold text-gray-800 mb-6">Lista de Tareas</h1>
        
        <Form newTodo={newTodo} setNewTodo={setNewTodo} addTodo={addTodo} />

        <div className="space-y-3">
          {todos.map(todo => (
            <Todo key={todo.id} todo={todo} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />
          ))}
        </div>

        {todos.length === 0 && (
          <div className="text-center text-gray-500 mt-6">
            No hay tareas pendientes. ¡Añade una!
          </div>
        )}
      </div>
    </div>
  );
}
```

A continuación explicamos sus funcionalidades más destacadas.

**Importaciones de componentes y uso de** `useState`:

```javascript
import { useState } from 'react';
import Form from './components/Form';
import Todo from './components/Todo';
```

* Se importa el hook `useState` para manejar el estado dentro del componente.
    
* También se importan dos componentes hijos, `Form` y `Todo`, que se encargan de manejar el formulario para agregar tareas y la representación de cada tarea respectivamente.
    

**Estados del componente**:

```javascript
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState('');
```

* `todos`: Un arreglo que contiene todas las tareas creadas. Cada tarea es un objeto con las propiedades `id`, `text`, y `completed`.
    
* `newTodo`: Almacena el texto de la nueva tarea que el usuario está escribiendo.
    

**Función** `addTodo` para agregar nuevas tareas:

```javascript
const addTodo = (e) => {
  e.preventDefault();
  if (newTodo.trim()) {
    setTodos([...todos, { id: Date.now(), text: newTodo, completed: false }]);
    setNewTodo('');
  }
};
```

* Esta función se ejecuta cuando se envía el formulario para agregar una nueva tarea.
    
* `e.preventDefault()`: Previene que la página se recargue al enviar el formulario.
    
* **Validación**: Se asegura de que el texto no esté vacío o compuesto solo de espacios en blanco con `newTodo.trim()`.
    
* **Agregar la tarea**: Si el texto es válido, se añade una nueva tarea al arreglo `todos`. La tarea tiene un `id` único basado en la fecha actual ([`Date.now`](http://Date.now)`()`), el texto (`newTodo`), y un estado `completed: false` (indica que la tarea no está completada).
    
* Después de agregar la tarea, se limpia el campo de texto con `setNewTodo('')`.
    

**Función** `toggleTodo` para actualizar tareas completadas:

```javascript
const toggleTodo = (id) => {
    setTodos(
      todos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };
```

La función `toggleTodo` se utiliza para alternar el estado de completado de una tarea en una lista de tareas. Aquí te explico cómo funciona:

* **Parámetro** `id`: La función recibe un parámetro `id`, que representa el identificador único de la tarea que se desea actualizar.
    
* `setTodos`: Esta es una función que se utiliza para actualizar el estado de las tareas.
    
* [`todos.map`](http://todos.map): La función `map` se utiliza para crear un nuevo arreglo de tareas basado en el arreglo existente `todos`. Para cada tarea en el arreglo, se verifica si su `id` coincide con el `id` proporcionado a la función `toggleTodo`.
    
* **Condición** [`todo.id`](http://todo.id) `=== id`: Si el `id` de la tarea actual coincide con el `id` proporcionado, se crea un nuevo objeto de tarea con todas las propiedades de la tarea actual (`{ ...todo }`), pero con el estado `completed` invertido (`!todo.completed`). Esto significa que si la tarea estaba marcada como completada, ahora estará pendiente, y viceversa.
    
* **Retorno de** `map`: Si el `id` no coincide, la tarea se devuelve sin cambios.
    

**Función** `deleteTodo` para eliminar tareas completadas:

```javascript
const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };
```

La función `deleteTodo` se utiliza para eliminar una tarea de la lista de tareas:

* **Parámetro** `id`: La función recibe un parámetro `id`, que representa el identificador único de la tarea que se desea eliminar.
    
* `setTodos`: Esta es una función que se utiliza para actualizar el estado de las tareas.
    
* `todos.filter`: La función `filter` se utiliza para crear un nuevo arreglo de tareas. Recorre cada tarea en el arreglo `todos` y aplica una condición.
    
* **Condición** [`todo.id`](http://todo.id) `!== id`: La condición verifica si el `id` de la tarea actual no coincide con el `id` proporcionado. Si no coincide, la tarea se incluye en el nuevo arreglo, el nuevo arreglo contiene todas las tareas excepto aquella cuyo `id` coincide con el `id` proporcionado, efectivamente eliminando esa tarea de la lista.
    

**Incorporación de los componentes** `Form` y `Todo` :

Para incorporar el formulario lo hacemos con la siguiente línea:

```xml
<Form newTodo={newTodo} setNewTodo={setNewTodo} addTodo={addTodo} />
```

Este componente es responsable de renderizar el formulario que permite a los usuarios agregar nuevas tareas. Recibe tres propiedades para gestionar el estado y las acciones del formulario.

* `newTodo={newTodo}`: Representa el estado actual del texto de la nueva tarea que el usuario está escribiendo. Se utiliza para controlar el valor del campo de entrada de texto en el formulario.
    
* `setNewTodo={setNewTodo}`: Se utiliza para actualizar el estado de `newTodo` cada vez que el usuario escribe en el campo de entrada. Esto permite que el componente mantenga el control del valor del campo de texto.
    
* `addTodo={addTodo}`: Se ejecuta cuando el usuario envía el formulario. Su propósito es agregar la nueva tarea a la lista de tareas, utilizando el texto almacenado en `newTodo`.
    

Para incorporar el componente `Todo` lo hacemos con la siguiente línea:

```xml
        <div className="space-y-3">
          {todos.map(todo => (
            <Todo key={todo.id} todo={todo} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />
          ))}
        </div>
```

Aquí se explica cada parte del código:

* `{`[`todos.map`](http://todos.map)`(todo => ( ... ))}`: Itera sobre un arreglo llamado `todos`. Por cada elemento `todo` en el arreglo, se ejecuta la función de flecha `(todo => ( ... ))`, que devuelve un componente `Todo`.
    
* `<Todo key={`[`todo.id`](http://todo.id)`} todo={todo} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />`: Este componente `Todo` se le pasan varias propiedades:
    
    * `key={`[`todo.id`](http://todo.id)`}`: La propiedad `key` es un identificador único para cada elemento en la lista, lo cual ayuda a React a identificar qué elementos han cambiado, agregado o eliminado.
        
    * `todo={todo}`: Pasa el objeto `todo` actual como una propiedad al componente `Todo`.
        
    * `toggleTodo={toggleTodo}`: Pasa la función `toggleTodo` como una propiedad, que se utiliza dentro del componente `Todo` para alternar el estado de completado de la tarea.
        
    * `deleteTodo={deleteTodo}`: Pasa la función `deleteTodo` como una propiedad, que se utiliza dentro del componente `Todo` para eliminar la tarea.
        

Con esto hemos finalizado la construcción del front-end de la aplicación Todo. Ahora vamos a realizar la conexión con la base de datos en FaunaDB.

### FaunaDB - Creación de la base de datos

Ingresa a tu cuenta en FaunaDB, en el panel de control de tu cuenta, haz clic en el botón “Create Database” y ponle un nombre. Yo lo nombré “todoapp”.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1731959627764/005e88af-f136-450d-bb8a-d2a741a0daaf.gif align="center")

Una vez creada la base de datos podemos crear la colección que contendrá las tareas. Para eso en el “shell” escribe la siguiente instrucción `Collection.create({ name: "Todo" })` :

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1740522709304/3aa44b83-e46a-4d70-99a2-537baf45b12e.gif align="center")

Continuamos con la creación del 'key secret’ que permitirá a nuestra aplicación realizar las operaciones CRUD.

### FaunaDB - Creación del ‘key secret’

Para crear la clave secreta, hay que dirigirse a la pestaña ‘Keys’, hacer clic en el botón ‘Create Key’. Aparecerá una ventana, en ella elige un rol, ponle un nombre a la clave y haz clic en ‘Save’.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1732303062732/4bb7ec2f-f193-4fa6-92ff-e7144871a81f.gif align="center")

La clave aparecerá sólo una vez, una vez que cierres la ventana ya no podrás acceder a ella. Copia la clave, vuelve al editor y en la raíz del proyecto crea un fichero `.env` para variables de entorno. Crea una nueva variable `VITE_REACT_APP_FAUNA_SECRET` y pega la clave.

```apache
VITE_REACT_APP_FAUNA_SECRET=fnAFw2Cje_AAQn7vMLAlaUHnoXXlyfZROpb0zTO3
```

(Tanto la base de datos como la clave están eliminados al momento de la publicación del artículo).

Continuamos con la instalación y configuración de la biblioteca `fauna` .

### Instalación y configuración de fauna package

Este paquete permitirá conectar la aplicación con la base de datos y realizar las operaciones de lectura y escritura. Realiza la instalación con el siguiente comando:

```plaintext
npm install fauna
```

Ahora crea un fichero `src/lib/fauna.js` que se encargará de realizar la conexión con la base de datos.

```javascript
import { Client, fql } from "fauna"

// Asegúrate de crear un archivo .env para almacenar tu clave secreta
const client = new Client({
  secret: import.meta.env.VITE_REACT_APP_FAUNA_SECRET,
})

export default client
```

Ahora en el mismo directorio crea un fichero `src/lib/todoDb.js` que tendrá las funciones CRUD de la aplicación.

```javascript
import client from './fauna'
import { fql } from 'fauna'

export const todoDb = {
  // Obtener todas las tareas
  getAllTodos: async () => {
    try {
      const result = await client.query(fql`
        Todo.all()
      `)
      return result.data
    } catch (error) {
      console.error('Error al obtener las tareas:', error)
      return []
    }
  },

  // Crear una nueva tarea
  createTodo: async (todoData) => {
    try {
      const result = await client.query(fql`
        Todo.create({
          text: ${todoData.text},
          completed: false
        })
      `)
      return result.data
    } catch (error) {
      console.error('Error al crear la tarea:', error)
      throw error
    }
  },

  // Actualizar una tarea
  updateTodo: async (todoId, updates) => {
    try {
      const result = await client.query(fql`
        Todo.byId(${todoId})!.update({
          completed: ${updates.completed}
        })
      `)
      return result.data
    } catch (error) {
      console.error('Error al actualizar la tarea:', error)
      throw error
    }
  },

  // Eliminar una tarea
  deleteTodo: async (todoId) => {
    try {
      await client.query(fql`
        Todo.byId(${todoId})!.delete()
      `)
      return true
    } catch (error) {
      console.error('Error al eliminar la tarea:', error)
      throw error
    }
  }
}
```

### Explicación de las funciones principales:

1. `getAllTodos` (Obtener todas las tareas):
    
    * Consulta la base de datos y obtiene todas las tareas almacenadas.
        
    * Si hay un error, lo captura y devuelve un array vacío.
        
2. `createTodo` (Crear una nueva tarea):
    
    * Recibe un objeto con los datos de una nueva tarea.
        
    * Inserta la tarea en la base de datos con el texto proporcionado y el estado `completed: false` por defecto.
        
    * Si ocurre un error, lo captura y lo lanza.
        
3. `updateTodo` (Actualizar una tarea):
    
    * Recibe el ID de la tarea y los datos a actualizar.
        
    * Modifica el estado de la tarea (`completed`) en la base de datos.
        
    * Captura y lanza errores si ocurren.
        
4. `deleteTodo` (Eliminar una tarea):
    
    * Recibe el ID de una tarea y la elimina de la base de datos.
        
    * Si la operación tiene éxito, devuelve `true`.
        
    * Captura errores y los lanza.
        

A continuación debemos actualizar el componente `src/App.jsx` como sigue:

```javascript
import { useState, useEffect } from 'react';
import Form from './components/Form';
import Todo from './components/Todo';
import { todoDb } from './lib/todoDb';

export default function TodoApp() {
  const [todos, setTodos] = useState([]);
  const [newTodo, setNewTodo] = useState('');
  const [loading, setLoading] = useState(true);

  // Cargar tareas al iniciar
  useEffect(() => {
    loadTodos();
  }, []);

  const loadTodos = async () => {
    try {
      const { data } = await todoDb.getAllTodos();
      setTodos(data);
    } catch (error) {
      console.error('Error al cargar las tareas:', error);
    } finally {
      setLoading(false);
    }
  };

  const addTodo = async (e) => {
    e.preventDefault();
    if (newTodo.trim()) {
      try {
        const newTodoData = await todoDb.createTodo({ text: newTodo });
        setTodos([...todos, newTodoData]);
        setNewTodo('');
      } catch (error) {
        console.error('Error al añadir la tarea:', error);
      }
    }
  };

  const toggleTodo = async (id) => {
    try {
      const todoToUpdate = todos.find(todo => todo.id === id);
      const updatedTodo = await todoDb.updateTodo(id, { 
        completed: !todoToUpdate.completed 
      });
      setTodos(todos.map(todo =>
        todo.id === id ? updatedTodo : todo
      ));
    } catch (error) {
      console.error('Error al actualizar la tarea:', error);
    }
  };

  const deleteTodo = async (id) => {
    try {
      await todoDb.deleteTodo(id);
      setTodos(todos.filter(todo => todo.id !== id));
    } catch (error) {
      console.error('Error al eliminar la tarea:', error);
    }
  };

  if (loading) {
    return <div className="text-center mt-8">Cargando...</div>;
  }

  return (
    <div className="min-h-screen bg-gray-100 py-8">
      <div className="max-w-md mx-auto bg-white rounded-lg shadow-lg p-6">
        <h1 className="text-2xl font-bold text-gray-800 mb-6">Lista de Tareas</h1>
        
        <Form newTodo={newTodo} setNewTodo={setNewTodo} addTodo={addTodo} />

        <div className="space-y-3">
          {todos.map(todo => (
            <Todo 
              key={todo.id} 
              todo={todo} 
              toggleTodo={toggleTodo} 
              deleteTodo={deleteTodo} 
            />
          ))}
        </div>

        {todos.length === 0 && (
          <div className="text-center text-gray-500 mt-6">
            No hay tareas pendientes. ¡Añade una!
          </div>
        )}
      </div>
    </div>
  );
}
```

De esta manera tenemos integrado las funciones CRUD del cliente de Fauna a nuestra aplicación.

### Probar la aplicación

Para probar la aplicación corre el comando `npm run dev` y prueba añadir, actualizar y eliminar una tarea, los datos deberían verse reflejados en la base de datos en fauna:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1740602988903/de7a2247-5b7d-4238-aecd-408ca9e9f930.gif align="center")

### Conclusión

En este tutorial, hemos construido una aplicación **TODO con ReactJS** y la hemos integrado con **FaunaDB** para manejar el almacenamiento de tareas. Aprendimos a conectar nuestra aplicación con la base de datos, realizar operaciones CRUD (**Crear, Leer, Actualizar y Eliminar tareas**) y gestionar la comunicación con el backend de forma eficiente.

FaunaDB nos ofrece una base de datos serverless, escalable y fácil de manejar con consultas FQL, lo que hace que sea una excelente opción para aplicaciones modernas con React. Además, este enfoque nos permite trabajar con una arquitectura sin servidor, reduciendo la necesidad de infraestructura compleja.

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

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!👋😊
