<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ React - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Descubre miles de cursos de programación escritos por expertos. Aprende Desarrollo Web, Ciencia de Datos, DevOps, Seguridad y obtén asesoramiento profesional para desarrolladores. ]]>
        </description>
        <link>https://www.freecodecamp.org/espanol/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ React - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/espanol/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 22 May 2026 15:16:32 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/espanol/news/tag/react/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Cómo construir una aplicación CRUD usando React y Convex ]]>
                </title>
                <description>
                    <![CDATA[ Las operaciones CRUD son la base de cada aplicación, por lo que es esencial volverse competente en esto cuando se aprende nuevas tecnologías. En este tutorial, aprenderás cómo construir una aplicación CRUD usando React y Convex. Cubriremos estas operaciones al construir un proyecto que se llama Book Collections. En este ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/como-construir-una-aplicacion-crud-usando-react-y-convex/</link>
                <guid isPermaLink="false">68102fd4c8db8f04fae91582</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ convex ]]>
                    </category>
                
                    <category>
                        <![CDATA[ crud ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Elias Ezequiel Pereyra Gomez ]]>
                </dc:creator>
                <pubDate>Fri, 27 Jun 2025 01:18:13 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2025/04/Captura-desde-2025-04-28-22-49-09.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Las operaciones CRUD son la base de cada aplicación, por lo que es esencial volverse competente en esto cuando se aprende nuevas tecnologías.</p><p>En este tutorial, aprenderás cómo construir una aplicación CRUD usando React y Convex. Cubriremos estas operaciones al construir un proyecto que se llama Book Collections. En este proyecto, los usuarios serán capaz de agregar libros y actualizar sus estados una vez que completan un libro.</p><h2 id="tabla-de-contenido"><strong><strong>Tabl</strong>a<strong> </strong>de<strong> </strong>Contenido</strong></h2><!--kg-card-begin: markdown--><ul>
<li><a href="#que-es-convex">¿Qué es Convex?</a></li>
<li><a href="#como-configurar-proyecto">¿Cómo configurar tu proyecto?</a></li>
<li><a href="#como-crear-esquema">¿Cómo crear el esquema?</a></li>
<li><a href="#como-crear-ui">¿Cómo crear la UI?</a></li>
<li><a href="#como-crear-funciones-crud">¿Cómo crear las funciones CRUD?</a></li>
<li><a href="#estilos">Los estilos</a></li>
<li><a href="#resumen">Resumen</a></li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: html--><h2 id="que-es-convex">¿Qué es Convex?</h2><!--kg-card-end: html--><p>Convex es la Plataforma BaaS que simplifica el desarrollo backend. Convex viene con una base de datos de tiempo real, y no tienes que preocuparte sobre escribir lógica de parte del servidor de forma separada porque provee métodos para solicitar y mutar la base de datos.</p><h3 id="pre-requisitos"><strong><strong>Pre</strong>-requisitos</strong></h3><p>Para seguir este tutorial, debes conocer los fundamentos de React. Estaré usando TypeScript en este proyecto, pero es opcional, así que puedes seguirme con JavaScript.</p><!--kg-card-begin: html--><h2 id="como-configurar-proyecto">Cómo configurar tu proyecto</h2><!--kg-card-end: html--><p>Crea una carpeta separa para el proyecto y llámalo como gustes – yo lo llamaré <strong>Books</strong>. Configuraremos Convex y React en esa carpeta.</p><p>Puedes crear una aplicación de React usando este comando:</p><pre><code class="language-bash">npm create vite@latest my-app -- --template react-ts
</code></pre><p>Si quieres trabajar con JavaScript, entonces quita el `ts` al final. Sería:</p><pre><code class="language-bash">npm create vite@latest my-app -- --template react
</code></pre><h3 id="c-mo-configurar-convex"><strong>Cómo configurar<strong> Convex</strong></strong></h3><p>Tenemos que instalar Convex en la misma carpeta. Puedes hacer eso usando este comando:</p><pre><code class="language-bash">npm install convex
</code></pre><p>Luego, ejecuta <code>npx convex dev</code>. Si lo estás haciendo por primera vez, te debería pedirte la autenticación. De otra forma, debería preguntarte por el nombre del proyecto.</p><p>Puedes visitar el <a href="https://www.convex.dev/">panel de Convex</a> para ver el proyecto que has creado.</p><p>Ahora que hemos configurado Convex y React, necesitamos conectar el backend de Convex a la aplicación de React.</p><p>En el <strong><code>src/main.tsx</code>, </strong>envuelve tu componente <code>App</code> con <code>ConvexReactClient</code>:</p><pre><code class="language-tsx">import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import { ConvexProvider, ConvexReactClient } from "convex/react";
import "./index.css"

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);

createRoot(document.getElementById("root")!).render(
  &lt;ConvexProvider client={convex}&gt;
    &lt;App /&gt;
  &lt;/ConvexProvider&gt;
);
</code></pre><p>Cuando configuras Convex, se crea un archivo <code>.env.local</code>. Puedes ver el URL de tu backend en ese archivo.</p><p>En la línea de abajo, instanciamos el Cliente de Convex de React con el URL.</p><pre><code class="language-typescript">const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
</code></pre><!--kg-card-begin: html--><h2 id="como-crear-esquema">Cómo crear el Esquema</h2><!--kg-card-end: html--><p>En tu carpeta principal del proyecto, deberías de ver la carpeta <strong>convex</strong>. Manejaremos las solicitudes de la base de datos y las mutaciones aquí.</p><p>Crea un archivo <strong>schema.ts</strong> en la carpeta <strong>convex:</strong></p><pre><code class="language-typescript">import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  books: defineTable({
    title: v.string(),
    author: v.string(),
    isCompleted: v.boolean(),
  }),
});
</code></pre><p>Puedes definir un Esquema para tu documento con <code>defineSchema</code> y crea un tabla con <code>defineTable</code>. Convex provee estas funciones para definir un esquema y crear una tabla.</p><p><code>v</code> es el validador de tipo, se usa para proveer tipos para cada dato que agregamos a la tabla.</p><p>Para este proyecto, ya que es una aplicación de colección de libros, la estructura tendrá <code>title</code>, <code>author</code>, y <code>isCompleted</code>. Puedes agregar mas campos.</p><p>Ahora que tienes definido tu esquema, configuremos la UI básica en React.</p><!--kg-card-begin: html--><h2 id="como-crear-ui">Cómo crear la UI</h2><!--kg-card-end: html--><p>En la carpeta <strong>src</strong>, crea una carpeta llamada <strong>component</strong> y un archivo <strong>Home.tsx</strong>. Aquí, puedes definir la UI.</p><pre><code class="language-tsx">import { useState } from "react";
import "../styles/home.css";

const Home = () =&gt; {
  const [title, setTitle] = useState("");
  const [author, setAuthor] = useState("");
  return (
    &lt;div className="main-container"&gt;
      &lt;h1&gt;Book Collections&lt;/h1&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;input
          type="text"
          name="title"
          value={title}
          onChange={(e) =&gt; setTitle(e.target.value)}
          placeholder="book title"
        /&gt;
        &lt;br /&gt;
        &lt;input
          type="text"
          name="author"
          value={author}
          onChange={(e) =&gt; setAuthor(e.target.value)}
          placeholder="book author"
        /&gt;
        &lt;br /&gt;
        &lt;input type="submit" /&gt;
      &lt;/form&gt;
      {books ? &lt;Books books={books} /&gt; : "Loading..."}
    &lt;/div&gt;
  );
};

export default Home;
</code></pre><p>Puedes crear tu componente como gustes. Agregué dos campos input <code>title</code>, <code>author</code>, y un botón <code>submit</code>. Esta es la estructura básica. Ahora podemos crear los métodos CRUD en el backend.</p><!--kg-card-begin: html--><h2 id="como-crear-funciones-crud">Cómo crear las funciones CRUD</h2><!--kg-card-end: html--><p>En la carpeta <strong>convex</strong>, puedes crear un archivo <strong>queries.ts</strong> separado para las funciones CRUD.</p><h3 id="la-funci-n-create"><strong>La<strong> </strong>Función Create</strong></h3><p>Ie <strong><strong>convex/queries.ts</strong></strong>:</p><p>Puedes definir una función <code>createBooks</code>. Puedes usar la función <code>mutation</code> de Convex para crear, actualizar, y eliminar datos. Leer los datos será a través de <code>query</code>.</p><p>La función <code>mutation</code> espera estos argumentos:</p><ul><li><code>agrs</code>: los datos que necesitamos almacenar en la base de datos.</li><li><code>handler</code>: maneja la lógica para almacenar los datos en la base de datos. El <code>handler</code> es una función asíncrona, y tiene dos argumentos: <code>ctx</code> y <code>args</code>. Aquí, <code>ctx</code> es el objeto contexto que usaremos para manejar las operaciones de la base de datos.</li></ul><p>Usarás el método <code>insert</code> para ingresar nuevos datos. El primer parámetro en el <code>insert</code> es el nombre de la tabla y el segundo es para los datos que necesitan ser insertados.</p><p>Como último, puedes regresar los datos desde la base de datos.</p><p>Aquí estás el código:</p><pre><code class="language-typescript">import { mutation} from "./_generated/server";
import { v } from "convex/values";

export const createBooks = mutation({
  args: { title: v.string(), author: v.string() },
  handler: async (ctx, args) =&gt; {
    const newBookId = await ctx.db.insert("books", {
      title: args.title,
      author: args.author,
      isCompleted: false,
    });
    return newBookId;
  },
});
</code></pre><h3 id="la-funci-n-read"><strong>La Función <strong>Read</strong></strong></h3><p>En <strong><strong>convex/queries.ts</strong></strong>:</p><pre><code class="language-typescript">import { query } from "./_generated/server";
import { v } from "convex/values";

//read
export const getBooks = query({
  args: {},
  handler: async (ctx) =&gt; {
    return await ctx.db.query("books").collect();
  },
});
</code></pre><p>En esta operación read, usamos la función incorporada <code>query</code> de Convex. Aquí, <code>args</code> estará vacía ya que no obtenemos ningún datos del usuario. De forma similar, la función <code>handler</code> es asíncrona y usa el objeto <code>ctx</code> para solicitar de la base de datos y regresar los datos.</p><h3 id="la-funci-n-update"><strong>La Función <strong>Update</strong></strong></h3><p>En <strong><strong>convex/queries.ts</strong></strong>:</p><p>Crea una función <code>updateStatus</code>. Vamos a actualizar solamente el estado <code>isCompleted</code>.</p><p>Aquí, necesitas obtener el ID del documento y el estado del usuario. En el <code>args</code>, definiremos el <code>id</code> y el <code>isCompleted</code>, los cuales vendrán del usuario.</p><p>En el <code>handler</code>, usaremos el método <code>patch</code> para actualizar los datos. El método <code>patch</code> espera dos argumentos: el primer argumento es el <code>id</code> del documento y el segundo es para los datos actualizados.</p><pre><code class="language-typescript">import { mutation } from "./_generated/server";
import { v } from "convex/values";

//update
export const updateStatus = mutation({
  args: { id: v.id("books"), isCompleted: v.boolean() },
  handler: async (ctx, args) =&gt; {
    const { id } = args;
    await ctx.db.patch(id, { isCompleted: args.isCompleted });
    return "updated"
  },
});
</code></pre><h3 id="funci-n-delete"><strong>Función <strong>Delete</strong></strong></h3><p>En <strong><strong>convex/queries.ts</strong></strong>:</p><p>Crea una función <code>deleteBooks</code> y usa la función <code>mutation</code>. Necesitaremos el ID del documento para que se elimine. En el <code>args</code>, define un ID. En el <code>handler</code>, usa el método <code>delete</code> del objeto <code>ctx</code>, y pasa el ID. Esto eliminará el documento.</p><pre><code class="language-typescript">import { mutation } from "./_generated/server";
import { v } from "convex/values";

//delete
export const deleteBooks = mutation({
  args: { id: v.id("books") },
  handler: async (ctx, args) =&gt; {
    await ctx.db.delete(args.id);
    return "deleted";
  },
});
</code></pre><p>A partir de ahora, has completado las funciones CRUD en el backend. Ahora necesitamos hacerlo funcionar en el UI. Volvamos a React.</p><h3 id="actualizar-el-ui"><strong>Actualizar<strong> </strong>el<strong> UI</strong></strong></h3><p>Ya has creado un UI básico en la aplicación de React, con algunos campos input. Vamos a actualizarlo.</p><p>En <strong><strong>src/component/Home.tsx</strong></strong>:</p><pre><code class="language-tsx">import { useQuery, useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Books } from "./Books";
import { useState } from "react";
import "../styles/home.css";

const Home = () =&gt; {
  const [title, setTitle] = useState("");
  const [author, setAuthor] = useState("");
  const books = useQuery(api.queries.getBooks);
  const createBooks = useMutation(api.queries.createBooks);

  const handleSubmit = (e: React.FormEvent&lt;HTMLFormElement&gt;): void =&gt; {
    e.preventDefault();
    createBooks({ title, author })
      .then(() =&gt; {
        console.log("created");
        setTitle("");
        setAuthor("");
      })
      .catch((err) =&gt; console.log(err));
  };
  return (
    &lt;div className="main-container"&gt;
      &lt;h1&gt;Book Collections&lt;/h1&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;input
          type="text"
          name="title"
          value={title}
          onChange={(e) =&gt; setTitle(e.target.value)}
          placeholder="book title"
        /&gt;
        &lt;br /&gt;
        &lt;input
          type="text"
          name="author"
          value={author}
          onChange={(e) =&gt; setAuthor(e.target.value)}
          placeholder="book author"
        /&gt;
        &lt;br /&gt;
        &lt;input type="submit" /&gt;
      &lt;/form&gt;
      {books ? &lt;Books books={books} /&gt; : "Loading..."}
    &lt;/div&gt;
  );
};

export default Home;
</code></pre><p>Ahora podemos usar las funciones API del backend al usar <code>api</code> de Convex. Como puedes ver, llamamos a dos funciones de la API: puedes usar <code>useQuery</code> si vas a obtener datos y <code>useMutation</code> si quieres cambiar los datos. Ahora en este archivo, que estamos haciendo, dos operaciones que son create y read.</p><p>Obtuvimos todos los datos al usar este método:</p><pre><code class="language-typescript"> const books = useQuery(api.queries.getBooks);
</code></pre><p>El arreglo de objetos serán almacenados en la variable books.</p><p>Obtuvimos la función create del backend con esta línea de código:</p><pre><code class="language-typescript">const createBooks = useMutation(api.queries.createBooks);
</code></pre><h3 id="c-mo-usar-la-funci-n-create-en-el-ui"><strong>Cómo usar la función<strong> </strong>create<strong> </strong>en el<strong> UI</strong></strong></h3><p>Usemos la función create en el UI.</p><p>Ya que los campos input están en la etiqueta <code>form</code>, usaremos el atributo <code>onSubmit</code> para manejar el envío del formulario.</p><pre><code class="language-typescript">// En el Home.tsx

const handleSubmit = (e: React.FormEvent&lt;HTMLFormElement&gt;): void =&gt; {
    e.preventDefault();
    createBooks({ title, author })
      .then(() =&gt; {
        console.log("created");
        setTitle("");
        setAuthor("");
      })
      .catch((err) =&gt; console.log(err));
  };
</code></pre><p>Cuando haces clic en submit, dispara la función <code>handleSubmit</code>.</p><p>Usamos el <code>createBooks</code> para pasar el <code>title</code> y <code>author</code> del estado. La función del endpoint es async, por lo que podemos usar el <code>handleSubmit</code> como async o usar <code>.then</code>. Usé el método <code>.then</code> para manejar los datos asíncronos.</p><p>Puedes crear un componente separado para mostrar los datos solicitados de la base de datos. Los datos regresados están en el <strong>Home.tsx</strong>, así que pasaremos los datos al componente <strong>Book.jsx</strong> como props.</p><p>En <strong><strong>Books.tsx</strong></strong>:</p><pre><code class="language-tsx">import { useState } from "react";
import { book } from "../types/book.type";
import { useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Id } from "../../convex/_generated/dataModel";
import "../styles/book.css";

export const Books = ({ books }: { books: book[] }) =&gt; {
  const [update, setUpdate] = useState(false);
  const [id, setId] = useState("");

  const deleteBooks = useMutation(api.queries.deleteBooks);
  const updateStatus = useMutation(api.queries.updateStatus);

  const handleClick = (id: string) =&gt; {
    setId(id);
    setUpdate(!update);
  };

  const handleDelete = (id: string) =&gt; {
    deleteBooks({ id: id as Id&lt;"books"&gt; })
      .then((mess) =&gt; console.log(mess))
      .catch((err) =&gt; console.log(err));
  };

  const handleUpdate = (e: React.FormEvent&lt;HTMLFormElement&gt;, id: string) =&gt; {
    e.preventDefault();
    const formdata = new FormData(e.currentTarget);
    const isCompleted: boolean =
      (formdata.get("completed") as string) === "true";
    updateStatus({ id: id as Id&lt;"books"&gt;, isCompleted })
      .then((mess) =&gt; console.log(mess))
      .catch((err) =&gt; console.log(err));
    setUpdate(false);
  };

  return (
    &lt;div&gt;
      {books.map((data: book, index: number) =&gt; {
        return (
          &lt;div
            key={data._id}
            className={`book-container ${data.isCompleted ? "completed" : "not-completed"}`}
          &gt;
            &lt;h3&gt;Book no: {index + 1}&lt;/h3&gt;
            &lt;p&gt;Book title: {data.title}&lt;/p&gt;
            &lt;p&gt;Book Author: {data.author}&lt;/p&gt;
            &lt;p&gt;
              Completed Status:{" "}
              {data.isCompleted ? "Completed" : "Not Completed"}
            &lt;/p&gt;
            &lt;button onClick={() =&gt; handleClick(data._id)}&gt;Update&lt;/button&gt;
            {id === data._id &amp;&amp; update &amp;&amp; (
              &lt;&gt;
                &lt;form onSubmit={(e) =&gt; handleUpdate(e, data._id)}&gt;
                  &lt;select name="completed"&gt;
                    &lt;option value="true"&gt;Completed&lt;/option&gt;
                    &lt;option value="false"&gt;Not Completed&lt;/option&gt;
                  &lt;/select&gt;
                  &lt;input type="submit" /&gt;
                &lt;/form&gt;
              &lt;/&gt;
            )}
            &lt;button onClick={() =&gt; handleDelete(data._id)}&gt;delete&lt;/button&gt;
          &lt;/div&gt;
        );
      })}
    &lt;/div&gt;
  );
};
</code></pre><p>En el componente <strong>Book.jsx, </strong>puedes mostrar los datos de la base de datos y manejar la funcionalidad para actualizar y eliminar los registros.</p><p>Veamos paso a paso cada una de estas características.</p><h3 id="c-mo-mostar-los-datos"><strong>Cómo mostar<strong> </strong>los<strong> Dat</strong>os</strong></h3><p>Puedes obtener los datos pasados como un prop en el componente <code>Home.tsx</code>. Si estás usando TypeScript, he definido un tipo para el objeto que es regresado de la solicitud. Puedes ignorar esto si estás usando JavaScript.</p><p>Crea <strong><strong><code>books.types.ts</code></strong></strong>:</p><pre><code class="language-typescript">export type book = {
    _id: string,
    title: string,
    author: string,
    isCompleted: boolean
}
</code></pre><p>Puedes usar la función <code>map</code> para mostrar los datos.</p><pre><code class="language-tsx">import { useState } from "react";
import { book } from "../types/book.type";
import { useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Id } from "../../convex/_generated/dataModel";
import "../styles/book.css";

export const Books = ({ books }: { books: book[] }) =&gt; {
  const [update, setUpdate] = useState(false);

  return (
    &lt;div&gt;
      {books.map((data: book, index: number) =&gt; {
        return (
          &lt;div
            key={data._id}
            className={`book-container ${data.isCompleted ? "completed" : "not-completed"}`}
          &gt;
            &lt;h3&gt;Book no: {index + 1}&lt;/h3&gt;
            &lt;p&gt;Book title: {data.title}&lt;/p&gt;
            &lt;p&gt;Book Author: {data.author}&lt;/p&gt;
            &lt;p&gt;
              Completed Status:{" "}
              {data.isCompleted ? "Completed" : "Not Completed"}
            &lt;/p&gt;
            &lt;button onClick={() =&gt; handleClick(data._id)}&gt;Update&lt;/button&gt;
            {id === data._id &amp;&amp; update &amp;&amp; (
              &lt;&gt;
                &lt;form onSubmit={(e) =&gt; handleUpdate(e, data._id)}&gt;
                  &lt;select name="completed"&gt;
                    &lt;option value="true"&gt;Completed&lt;/option&gt;
                    &lt;option value="false"&gt;Not Completed&lt;/option&gt;
                  &lt;/select&gt;
                  &lt;input type="submit" /&gt;
                &lt;/form&gt;
              &lt;/&gt;
            )}
            &lt;button onClick={() =&gt; handleDelete(data._id)}&gt;delete&lt;/button&gt;
          &lt;/div&gt;
        );
      })}
    &lt;/div&gt;
  );
};
</code></pre><p>Esta es la estructura básica. Mostramos el título, autor, y estado, junto con un botón actualizar y eliminar.</p><p>Ahora, agreguemos las funcionalidades.</p><pre><code class="language-tsx">import { useState } from "react";
import { book } from "../types/book.type";
import { useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Id } from "../../convex/_generated/dataModel";
import "../styles/book.css";

export const Books = ({ books }: { books: book[] }) =&gt; {
  const [update, setUpdate] = useState(false);
  const [id, setId] = useState("");

  const deleteBooks = useMutation(api.queries.deleteBooks);
  const updateStatus = useMutation(api.queries.updateStatus);

  const handleClick = (id: string) =&gt; {
    setId(id);
    setUpdate(!update);
  };

  const handleDelete = (id: string) =&gt; {
    deleteBooks({ id: id as Id&lt;"books"&gt; })
      .then((mess) =&gt; console.log(mess))
      .catch((err) =&gt; console.log(err));
  };

  const handleUpdate = (e: React.FormEvent&lt;HTMLFormElement&gt;, id: string) =&gt; {
    e.preventDefault();
    const formdata = new FormData(e.currentTarget);
    const isCompleted: boolean =
      (formdata.get("completed") as string) === "true";
    updateStatus({ id: id as Id&lt;"books"&gt;, isCompleted })
      .then((mess) =&gt; console.log(mess))
      .catch((err) =&gt; console.log(err));
    setUpdate(false);
  };

  return (
    &lt;div&gt;
      {books.map((data: book, index: number) =&gt; {
        return (
          &lt;div
            key={data._id}
            className={`book-container ${data.isCompleted ? "completed" : "not-completed"}`}
          &gt;
            &lt;h3&gt;Book no: {index + 1}&lt;/h3&gt;
            &lt;p&gt;Book title: {data.title}&lt;/p&gt;
            &lt;p&gt;Book Author: {data.author}&lt;/p&gt;
            &lt;p&gt;
              Completed Status:{" "}
              {data.isCompleted ? "Completed" : "Not Completed"}
            &lt;/p&gt;
            &lt;button onClick={() =&gt; handleClick(data._id)}&gt;Update&lt;/button&gt;
            {id === data._id &amp;&amp; update &amp;&amp; (
              &lt;&gt;
                &lt;form onSubmit={(e) =&gt; handleUpdate(e, data._id)}&gt;
                  &lt;select name="completed"&gt;
                    &lt;option value="true"&gt;Completed&lt;/option&gt;
                    &lt;option value="false"&gt;Not Completed&lt;/option&gt;
                  &lt;/select&gt;
                  &lt;input type="submit" /&gt;
                &lt;/form&gt;
              &lt;/&gt;
            )}
            &lt;button onClick={() =&gt; handleDelete(data._id)}&gt;delete&lt;/button&gt;
          &lt;/div&gt;
        );
      })}
    &lt;/div&gt;
  );
};
</code></pre><p>Este es todo el código del componente. Déjame explicarte qué hice.</p><p>Primero, necesitamos alternar la actualización, así que definimos la función <code>handleClick</code>, y le pasamos un ID del documento.</p><pre><code class="language-typescript">//handleClick
 const handleClick = (id: string) =&gt; {
    setId(id);
    setUpdate(!update);
  };
</code></pre><p>En el <code>handleClick</code> puedes actualizar el estado del ID y alternar el estado de la actualización de forma que alternará la actualización cuando se haga clic, y en otro clic, se cerrará.</p><p>Luego, tenemos <code>handleUpdate</code>. Necesitamos el ID del documento para actualizar los datos, así que pasamos el objeto evento así también como el ID del documento. Para obtener la entrada, podemos usar <code>FormData</code>.</p><pre><code class="language-typescript">const updateStatus = useMutation(api.queries.updateStatus);

const handleUpdate = (e: React.FormEvent&lt;HTMLFormElement&gt;, id: string) =&gt; {
    e.preventDefault();
    const formdata = new FormData(e.currentTarget);
    const isCompleted: boolean =
      (formdata.get("completed") as string) === "true";
    updateStatus({ id: id as Id&lt;"books"&gt;, isCompleted })
      .then((mess) =&gt; console.log(mess))
      .catch((err) =&gt; console.log(err));
    setUpdate(false);
  };
</code></pre><p>Necesitamos usar el <code>useMutation</code> para obtener la función <code>updateStatus</code>. Pasa el ID y el estado completado a la función, y maneja la parte asíncrona usando <code>.then</code>.</p><p>Para la función delete, el ID del documento es suficiente. Como el anterior, llama la función delete usando el <code>useMutation</code> y pásale el ID.</p><p>Luego pasa el ID del documento y maneja la promesa.</p><pre><code class="language-typescript">const deleteBooks = useMutation(api.queries.deleteBooks);

const handleDelete = (id: string) =&gt; {
    deleteBooks({ id: id as Id&lt;"books"&gt; })
      .then((mess) =&gt; console.log(mess))
      .catch((err) =&gt; console.log(err));
 };
</code></pre><!--kg-card-begin: html--><h2 id="estilos">Estilos</h2><!--kg-card-end: html--><p>Finalmente, lo que queda es agregar algo de estilo. Agregué algunos estilos básicos. Si el libro no ha sido completado, estará en rojo, y si el libro se ha completado, estará en verde.</p><p>Aquí la captura de pantalla:</p><figure class="kg-card kg-image-card"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729428111374/1d1a69ef-5d35-4410-91f4-d8cf4817991d.png" class="kg-image" alt="final output" width="851" height="601" loading="lazy"></figure><p>¡¡Esto es todo chicos!!</p><p>Puedes visitar mi repositorio para ver todo el código: <a href="https://github.com/sanjayr-12/convex-crud">convex-curd</a></p><!--kg-card-begin: html--><h2 id="resumen">Resumen</h2><!--kg-card-end: html--><p>En este artículo, implementamos las operaciones CRUD (Crear, Leer, Actualizar y Eliminar) al construir una aplicación de colecciones de libros. Comenzamos configurando Convex y React, y escribir la lógica de CRUD.</p><p>Este tutorial cubrió tanto el frontend como el backend, demostrando cómo construir una aplicación serverless.</p><p>Puedes encontrar todo el código aquí: <a href="https://github.com/sanjayr-12/convex-crud">convex-curd</a></p><p>Si hay algún error o cualquier duda, contáctame en <a href="https://www.linkedin.com/in/sanjay-r-ab6064294/">LinkedIn</a>, <a href="https://www.instagram.com/_sanjayxr_12_/">Instagram</a>.</p><p>¡Gracias por leer!</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Next.js vs React : Diferencias y cómo elegir el adecuado para tu proyecto ]]>
                </title>
                <description>
                    <![CDATA[ Como desarrollador, hay muchas herramientas, lenguajes, frameworks y paquetes de código abierto que tienes que aprender para que tu trabajo se facilite y sea sencillo (aunque el trayecto no es sencillo, lo lograrás). Algunas de estas herramientas, lenguajes, o frameworks son usados diariamente por miembros de la comunidad y pueden ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/next-js-vs-react-diferencias-y-como-elegir-el-adecuado-para-tu-proyecto/</link>
                <guid isPermaLink="false">67ff2616e738c10479d3f336</guid>
                
                    <category>
                        <![CDATA[ NextJS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ csr ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Elias Ezequiel Pereyra Gomez ]]>
                </dc:creator>
                <pubDate>Fri, 20 Jun 2025 15:06:15 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2025/04/Captura-desde-2025-04-16-00-38-46.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Como desarrollador, hay muchas herramientas, lenguajes, frameworks y paquetes de código abierto que tienes que aprender para que tu trabajo se facilite y sea sencillo (aunque el trayecto no es sencillo, lo lograrás).</p><p>Algunas de estas herramientas, lenguajes, o frameworks son usados diariamente por miembros de la comunidad y pueden padecer cambios fundamentales en cómo son implementados o escritos con el tiempo.</p><p>En este artículo, exploraremos dos tecnologías populares de JavaScript, Next.js y React.js, comparando sus diferencias claves, examinando sus fortalezas, y ofreciendo perspectivas para ayudar a los desarrolladores a elegir la mejor opción para sus proyectos.</p><h2 id="tabla-de-contenido"><strong><strong>Tabl</strong>a<strong> </strong>de<strong> </strong>Contenido</strong></h2><!--kg-card-begin: markdown--><ul>
<li><a href="#entendiendo-react">Entendiendo React</a>
<ul>
<li><a href="#renderizado-lado-cliente">Renderizado del lado del Cliente</a></li>
<li><a href="#casos-uso-react-desarrollo-web">Casos de uso para React en Desarrollo Web</a></li>
</ul>
</li>
<li><a href="#explorando-nextjs">Explorando Next.js</a>
<ul>
<li><a href="#renderizado-lado-servidor">Renderizado del lado del Servidor</a></li>
<li><a href="#casos-uso-nextjs-desarrollo-web">Casos de uso para Next.js en Desarrollo Web</a></li>
</ul>
</li>
<li><a href="#diferencia-clave-nextjs-react">Diferencias claves entre Next.js y React</a>
<ul>
<li><a href="#metodos-renderizado">Métodos de renderizado: lado del cliente vs. lado del servidor</a></li>
<li><a href="#consideraciones-rendimiento">Consideraciones de rendimiento</a></li>
<li><a href="#implicaciones-seo">Implicaciones sobre SEO</a></li>
<li><a href="#escalabilidad-complejudad-proyecto">Escalabilidad y complejidad del proyecto</a></li>
</ul>
</li>
<li><a href="#cuando-usar-react-nextjs">Cuándo usar React o Next.js</a>
<ul>
<li><a href="#cuando-usar-react">Cuándo usar React</a></li>
<li><a href="#cuando-usar-nextjs">Cuándo usar Next.js</a></li>
</ul>
</li>
<li><a href="#conclusion">Conclusión</a></li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: html--><h2 id="entendiendo-react">Entendiendo React</h2><!--kg-card-end: html--><p>Frecuentemente nos confundimos en cuanto si React es un framework de JavaScript o no, pero acá está la respuesta a esa pregunta. React no es un framework de JavaScript, sino una librería. Ahora, estas dos terminologías son intercambiables con frecuencia o mal usadas, pero los explicaré en breve.</p><p>Una librería es una colección de código ya escrito que puede ser re-usado o llamarse cuando construyes tu proyecto.</p><p><strong>Ejemplo</strong>: Imagina una biblioteca en donde vas a estudiar. Los libros ya están disponibles en los estantes – simplemente agarras uno que necesitas y comienzas a leer. De forma similar, en programación, una librería (o biblioteca) provee herramientas ya terminadas que puedes usar en tu proyecto sin tener que empezar de cero.</p><p>Por otro lado, un framework es como una estructura ya hecha que te ayuda a construir tu proyecto. Te da una fundación sólida para trabajar, de forma que no tengas que comenzar desde cero o escribir código repetitivo. En cambio, te enfocas en agregar tus propias características y lógica, mientras que el framework se encarga de ejecutar cosas en el momento exacto y de la forma correcta.</p><p><strong>Ejemplo</strong>: Imagina que un framework es como una casa en construcción donde los muros, el fundamento, y el techo ya están construidos. Todo lo que necesitas hacer es decidir cómo diseñar el interior – cómo elegir los muebles, la pintura y las decoraciones. El framework maneja el trabajo pesado, cómo asegurar que la casa se mantenga fuerte, mientras te enfocas en hacer lo tuyo. De forma similar, en programación, el framework provee la estructura, y tu agregas tu lógica personalizada para completar el proyecto.</p><p>Con eso fuera del camino, continuemos.</p><p>React es una de las librerías de JavaScript más populares usadas por los desarrolladores para construir interfaces de usuario rápidos, interactivos y confiables. Es una librería declarativa que ayuda a los desarrolladores crear aplicaciones web basado en componentes. Facebook desarrolló esta librería en 2011 y ha sido tendencia desde entonces.</p><p>Usualmente, cuando se escribe código JavaScript, creamos un archivo con la extensión <code>js</code>. Por ejemplo: <code>App.js</code>, <code>script.js</code>, y así sucesivamente. En React creamos un archivo con la extensión <code>jsx</code>. Eso es: <code>index.jsx</code>, <code>Home.jsx</code>, y así sucesivamente. El <code>jsx</code> es una extensión de React que te permite escribir un código de JavaScript que se parece a HTML. La sintaxis, cuando se ejecuta, pasa a través de pre-procesadores/transpiladores los cuales transforman el código parecido a HTML a código estándar de JavaScript.</p><p>En el corazón de todas las aplicaciones de React están los componentes. Los componentes son trozos de interfaces de usuario (UI) los cuales son hechos de forma independiente y pueden ser re-usados en partes distintas en tu proyecto. Distintos componentes pueden ser hechos de forma separada y luego se juntan para formar una interfaz de usuario compleja (UI).</p><p><strong>Nota</strong>: Cada aplicación de React tiene al menos un componente, comúnmente referido como el componente raíz. Este componente representa la aplicación entera. Dentro del componente raíz, con frecuencia hay otros componentes, conocidos como componentes hijos, que ayudan a estructurar y manejar distintas partes de la aplicación.</p><p>Acá hay una representación estructural de los componentes raíz e hijos.</p><figure class="kg-card kg-image-card"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732758941272/17c6b471-b2a7-40ae-83f8-4e215a50c853.png" class="kg-image" alt="structural representation of root and child components" width="939" height="753" loading="lazy"></figure><p>De la imagen de arriba, puedes entender con claridad de qué tratan los componentes. <code>App</code> es el componente raíz, y dentro del componente raíz tenemos los componentes hijos: <code>Navbar</code>, <code>Profile</code>, <code>Blog</code> y <code>Footer</code>. Los hijos componentes pueden ser re-usados en otras páginas del proyecto sin tener que re-escribir el código nuevamente.</p><!--kg-card-begin: html--><h3 id="renderizado-lado-cliente">Renderizado del lado del cliente</h3><!--kg-card-end: html--><p>El renderizado del lado del cliente (CSR – Client Side Rendering en inglés) es una técnica común, especialmente en librerías como React y frameworks como Vue.js, Angular, y así sucesivamente. Aquí, el navegador descarga y procesa los archivos de JavaScript para renderizar dinámicamente el contenido directamente en el dispositivo del usuario. Con CSR, las páginas web se generan de forma dinámica, y cualquier actualización o cambio al código se aplica sin requerir una recarga completa de página. Solamente las partes específicas que han cambiado se actualizan, asegurando una experiencia de usuario fluida y eficiente.</p><p>Por lo tanto, en CSR, la lógica y la estructura de la página web se maneja por el cliente (navegador) y una recarga completa de página se muestra.</p><p>Para ayudarte a entender CSR, <a href="https://www.freecodecamp.org/news/rendering-patterns/#heading-single-page-applications-spas-with-client-side-rendering-csr">he agregado un artículo aquí</a>. </p><!--kg-card-begin: html--><h3 id="casos-uso-react-desarrollo-web">Casos de uso para React en Desarrollo Web</h3><!--kg-card-end: html--><p>Desde que React se volvió la opción preferida de muchos desarrolladores, su flexibilidad lo ha hecho conveniente para un amplio rango de casos de uso en desarrollo web. Aquí hay unos pocos casos:</p><ul><li><strong>Aplicaciones de una Sola Página<strong> (SPAs):</strong></strong> Cuando hablamos sobre SPAs, no nos referimos realmente que tu aplicación web tiene solamente una página, puede tener múltiples páginas. En SPAs, tus archivos de aplicación web (HTML, CSS, JS) se generan una vez en tu página web y cuando las actualizaciones subsecuentes se realizan en el archivo, tu página no tendrá que recargar completamente. Este enfoque ayuda a asegurar una transición más rápida, reduce la carga en el servidor y mejorar la experiencia general de usuario.</li><li><strong>Interfaces de Usuario <strong>Interactiv</strong>os<strong>:</strong></strong> React es idóneo para construir interfaces de usuarios interactivos, de vez en cuando, pasa por actualizaciones dinámicas basadas en las acciones de los usuarios. Ejemplos son formularios en línea, paneles de control, sitios web (sitios web de comercio electrónico), y así sucesivamente.</li><li><strong>Aplicaciones Multiplataforma<strong>:</strong></strong> Tener conocimientos de React viene a la mano cuando se construyen aplicaciones móviles, simplificando la conexión entre aplicaciones web y aplicaciones móviles. Herramientas como React Native te ayudan a lograr este proceso.</li></ul><!--kg-card-begin: html--><h2 id="explorando-nextjs">Explorando Next.js</h2><!--kg-card-end: html--><p>Next.js es un framework popular basado en React usado para construir aplicaciones web con el uso de componentes de React. Next.js provee estructura adicional, características, y optimización para tu aplicación web.</p><p>A diferencia de React, Next.js soporta renderizado del lado del servidor (SSR), por lo que las solicitudes se procesan y se generan desde el servidor y luego mostrado en el navegador (cliente).</p><!--kg-card-begin: html--><h3 id="renderizado-lado-servidor">Renderizado del lado del Servidor</h3><!--kg-card-end: html--><p>El Renderizado del lado del servidor (SSR - en inglés "Server Side-Rendering") es una técnica en el desarrollo web donde un servidor genera el HTML para una página web en el servidor y lo envía al navegador (cliente). En otras palabras, el servidor maneja las estructuras y la lógica de la página y muestra una página totalmente renderizada en la pantalla.</p><p>En el renderizado del lado del servidor, una solicitud se envía primero al servidor (cliente), luego el servidor comienza a procesar la solicitud y cuando termina de procesar la solicitud, ejecuta la solicitud al generar y muestra un archivo HTML con el contenido en el navegador (lado del cliente). Cuando un cambio se hace o una nueva página se solicita, una nueva solicitud se envía nuevamente al servidor y es procesado nuevamente – un nuevo archivo HTML y totalmente renderizado será generado y mostrado en el navegador (cliente).</p><p>Para una mejor comprensión de CSR y SSR, agregué un <a href="https://youtu.be/-JXUaydU1J0?si=U3PrqicrIJoLYOM9">vídeo de YouTube aquí</a>.</p><!--kg-card-begin: html--><h3 id="casos-uso-nextjs-desarrollo-web">Casos de Uso para Next.js en el desarrollo web</h3><!--kg-card-end: html--><ul><li><strong>Aplicaciones de una Sola Página<strong> (SPAs):</strong></strong> Next.js puede ser usado en la creación de aplicaciones de una sola página, similar a React.</li><li><strong>Optimizado para <strong>SEO:</strong></strong> Next.js ayuda en crear sitios web optimizados para SEO al renderizar un archivo HTML en el servidor y entregarlo al navegador. Esto mejora la visibilidad en el motor de búsquedas, incrementando las chances que tu sitio web aparezca en la parte superior de los resultados de búsqueda.</li><li><strong>Plataformas de <strong>Multi-Us</strong>uarios<strong>:</strong></strong> Debido a la habilidad de Next.js de manejar el enrutamiento dinámico, el manejo de API, y así sucesivamente, es fácil de crear aplicaciones que sirven para diversos propósitos.</li></ul><!--kg-card-begin: html--><h2 id="diferencia-clave-nextjs-react">Diferencia clave entre Next.js y React</h2><!--kg-card-end: html--><!--kg-card-begin: html--><h3 id="metodos-renderizado">Métodos de renderizado: lado del cliente vs. lado del servidor</h3><!--kg-card-end: html--><p>Cuando hablamos sobre el método de renderizado en React, React se base principalmente en el método de renderizado del lado del cliente (CSR). Por lo tanto tanto la lógica como la estructura de la página web será manejado por el navegador (cliente). Aunque este método se usa comúnmente, tiene algunos efectos secundarios como carga de página inicial más lenta.</p><p>Next.js por el otro lado, soporta tanto SSR como CSR ya que fue construido por encima de React. Las páginas web se renderizan en el servidor y tanto la lógica como la estructura de la página se manejan por el servidor. Esto permite una carga más rápida de la página web y también mejora el SEO.</p><!--kg-card-begin: html--><h3 id="consideraciones-rendimiento">Consideraciones de rendimiento</h3><!--kg-card-end: html--><p>En términos de consideraciones de rendimiento, Next.js se prefiere frecuentemente porque ofrece múltiples opciones de renderizado, incluyendo renderizado del lado del servidor (SSR), generación de sitios estáticos (SSG), Regeneración estático incremental (ISR), y renderizado del lado del cliente (CSR). En contraste, React primariamente provee un enfoque de renderizado único: renderizado del lado del cliente.</p><!--kg-card-begin: html--><h3 id="implicaciones-seo">Implicaciones de SEO</h3><!--kg-card-end: html--><p>React está menos optimizado para SEO porque los motores de búsqueda puede tener dificultades al indexar contenido que requiere ejecución de JavaScript para renderizar.</p><p>Del otro lado, Next.js está más optimizado para SEO que React porque renderiza contenido en el servidor, provee HTML totalmente renderizado para los motores de búsqueda para facilitar el indexado.</p><!--kg-card-begin: html--><h3 id="escalabilidad-complejudad-proyecto">Escalabilidad y complejidad del Proyecto</h3><!--kg-card-end: html--><p>En términos de escalabilidad y complejidad del proyecto, Next.js es generalmente mejor que React. Next.js provee funciones integradas que mejoran la escalabilidad de tu proyecto. Estos incluyen:</p><ul><li>Renderizado del lado del servidor (SSR) y generación de sitios estáticos (SSG) para mejor rendimiento y SEO.</li><li>Una función de rutas API incorporada para crear funciones serverless sin problemas.</li><li>Un sistema de enrutamiento basado en archivos que simplifica la organización de proyectos más grandes.</li></ul><p>En contraste, con React, eres responsable de configurar y mantener la estructura para la escalabilidad. Para proyectos más grandes, esto frecuentemente requiere agregar herramientas adicionales tales como:</p><ul><li>Librerías de gestión de estado (por ejemplo, Redux, Recoil, y así sucesivamente)</li><li>Librerías de enrutamiento (por ejemplo, React Router)</li></ul><p>Estas herramientas son necesarias para mejorar la escalabilidad de React y abarcar la complejidad del proyecto, pero también incrementan la carga y esfuerzo necesario para configurar y gestionar la aplicación.</p><p>En resumen, aquí una tabla que lo desglosa:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Factores</th>
<th>React</th>
<th>Next.js</th>
</tr>
</thead>
<tbody>
<tr>
<td>Escalabilidad</td>
<td>Es posible pero para incrementar la escalabilidad, requiere herramientas adicionales y una configuración personalizada.</td>
<td>Es escalable y ya tiene herramientas integradas que incrementan la escalabilidad.</td>
</tr>
<tr>
<td>Rendimiento</td>
<td>Provee solamente una opción de renderizado el cual es el renderizado del lado del cliente (CSR).</td>
<td>Ofrece múltiples opciones de renderizado, incluyendo SSR, SSG, ISR, y CSR.</td>
</tr>
<tr>
<td>SEO</td>
<td>No está optimizado para el SEO porque a los motores de búsquedas les podría costar indexar el contenido que requiere ejecución de JavaScript para renderizar.</td>
<td>Está más optimizado para el SEO que React porque renderiza el contenido en el servidor, proveyendo HTML totalmente renderizado para los motores de búsqueda para indexar más fácilmente.</td>
</tr>
<tr>
<td>Caso de uso</td>
<td>Mayormente usado en proyectos más pequeños o únicos.</td>
<td>Mayormente usado en proyectos de gran escala y mejora el rendimiento el SEO.</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><!--kg-card-begin: html--><h2 id="cuando-usar-react-nextjs">Cuándo usar React o Next.js</h2><!--kg-card-end: html--><p>Elegir la herramienta correcta para tu proyecto depende únicamente en la complejidad de la solución del proyecto que estás construyendo. Mientras que React y Next.js están relacionados cercanamente, cada uno tiene sus fortalezas y casos de uso óptimos.</p><!--kg-card-begin: html--><h3 id="cuando-usar-react">Cuándo usar React</h3><!--kg-card-end: html--><p>Aquí hay algunos casos donde es mejor usar React para tu proyecto:</p><ul><li>Cuando construyes aplicaciones altamente interactivas.</li><li>Cuando tu proyecto requiere manejo manual de enrutamiento, gestión de estado o/e integración de API.</li><li>Cuando tu proyecto requiere renderizado del lado del cliente (CSR)</li></ul><!--kg-card-begin: html--><h3 id="cuando-usar-nextjs">Cuándo usar Next.js</h3><!--kg-card-end: html--><p>Aquí hay algunos casos donde es mejor usar Next.js:</p><ul><li>Cuando tu proyecto requiere un mejor SEO.</li><li>Cuando tu proyecto requiere renderizado del lado del servidor.</li><li>Cuando tu proyecto requiere que construyas APIs junto con tu código de frontend.</li><li>Cuando construyes sitios web basado en contenido como blogs o sitios e-commerce. Debido a su uso de renderizado del lado del servidor, ayuda en mejorar los tiempos de carga de los contenidos en la página.</li><li>Next.js se usa mejor cuando quieres optimizar imágenes en tu proyecto.</li></ul><!--kg-card-begin: html--><h2 id="conclusion">Conclusión</h2><!--kg-card-end: html--><p>A este punto creo que tienes una clara comprensión de React y de Next.js, los conceptos del renderizado del lado del servidor y del lado del cliente, los casos de uso tanto para React como para Next.js, y las diferencias claves entre ellos.</p><p>Gracias por tomarte el tiempo de leer este artículo. Espero que lo hayas encontrado útil.</p><p>Feliz codificación.</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Construir un juego de memoria en React ]]>
                </title>
                <description>
                    <![CDATA[   ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/build-un-juego-de-memoria/</link>
                <guid isPermaLink="false">67edc82d9118ef04ee3e70f3</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Isabela Mena Bueno ]]>
                </dc:creator>
                <pubDate>Thu, 08 May 2025 15:40:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2025/06/a0102079-c5ee-4eaa-8757-cf4d7740033c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Artículo original:</strong> <a href="https://www.freecodecamp.org/news/build-a-memory-game-in-react/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Build a Memory Game in React</a>
      </p><p>Construir aplicaciones web interactivas y accesibles es una habilidad crucial para los desarrolladores modernos. React, una de las bibliotecas de JavaScript más populares, ofrece una forma poderosa de crear interfaces de usuario dinámicas. Una gran manera de profundizar tu conocimiento de React mientras trabajas en un proyecto es crear un juego de memoria - un desafío atractivo que abarca conceptos clave del desarrollo, como la gestión del estado, el diseño de componentes y las interacciones del usuario.</p><p>Acabamos de publicar un curso en el canal de YouTube freeCodeCamp.org que te enseñará a construir un juego de memoria completamente interactivo en React, con un gran énfasis en accesibilidad. Este curso, desarrollado por Scrimba, te guía a través de todo el proceso de desarrollo, desde la obtención de datos de una API y el diseño de componentes reutilizables hasta la implementación de la lógica del juego y las mejores prácticas de accesibilidad. Al final del curso, tendrás un proyecto pulido y listo para mostrar tus habilidades.</p><p>El curso empieza cubriendo los aspectos fundamentales del juego, incluyendo la obtención y almacenamiento de datos de una API, la renderización de componentes dinámicos y la garantía de interacciones fluidas para el usuario. Aprenderás a recuperar y gestionar los datos de juego, barajar y aleatorizar los elementos, así como a manejar la lógica del juego, como la detección de coincidencias y el seguimiento del progreso.</p><p>La accesibilidad es un enfoque clave de este proyecto, garantizando que el juego sea inclusivo y fácil de usar. Explorarás técnicas esenciales de accesibilidad , incluyendo el uso de atributos ARIA , HTML semántico y navegación con teclado. Además, el curso profundiza en la gestión eficiente del estado de la aplicación, el manejo adecuado de errores y la refactorización de componentes para mejorar su mantenimiento.</p><p>Algunos de los temas claves que se abordan en este curso incluyen:</p><ul><li>Obtención y almacenamiento de datos de una API utilizando el estado de React.</li><li>Renderización y gestión de componentes dinámicos del juego.</li><li>Implementación de la lógica para seleccionar, emparejar y barajar elementos del juego.</li><li>Construcción de componentes reutilizables, como un botón interactivo de emoji </li><li>Mejora de la accesibilidad mediante atributos ARIA y consideraciones para tecnologías de asistencia.</li><li>Manejo de errores y mejora de la experiencia del usuario con componentes de interfaz dedicados</li><li>Gestión y refactorización de datos de formularios dentro del estado de React </li></ul><p>Al final de este curso no solo tendrás un juego de memoria completamente funcional si no que también obtendrás un conocimiento mas profundo del desarrollo de React y sus principios sobre la accesibilidad. Ya sea que estes buscando mejorar tus habilidades en React, crear un proyecto para tu portafolio o aprender más sobre desarrollo web inclusivo, este tutorial es un recurso excelente. </p><p>¡Mira el curso completo en el canal de YouTube de <a href="https://www.youtube.com/watch?v=MzVbgZgGON4">freeCodeCamp.org</a> y comienza hoy mismo a construir tu propio juego de memoria interactivo!</p><figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.49999999999999%;" class="fluid-width-video-wrapper">
            <iframe width="200" height="113" src="https://www.youtube.com/embed/MzVbgZgGON4?start=12412&amp;feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" title="Build a Memory Game in React Tutorial" name="fitvid0"></iframe>
          </div>
        </div>
      </figure><p>Si este artículo fue de ayuda, <a href="https://x.com/intent/post?text=Build%20a%20Memory%20Game%20in%20React%0A%0Ahttps://www.freecodecamp.org/news/build-a-memory-game-in-react/">¡Compártelo!</a></p><p>Aprende a programar gratis. El plan de estudios de código abierto de freeCodeCamp ha ayudado a más de 40,000 personas a conseguir empleo como desarrolladores. <a href="https://www.freecodecamp.org/learn/">¡Empieza hoy mismo!</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Como construir una app de chat en tiempo real con ReactJS y Firebase ]]>
                </title>
                <description>
                    <![CDATA[ En este artículo, voy a mostrarte como crear una app de chat en tiempo real usando React.js y Firebase. En la app, vamos a permitir que los usuarios se logueen con su cuenta de Google usando el servicio de inicio de sesion de Firebase. También guardaremos y recuperaremos todos los ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/como-construir-una-app-de-chat-en-tiempo-real-con-reactjs-y-firebase/</link>
                <guid isPermaLink="false">6638d45a29a7ee03fce3bb36</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Lucas Agustín Morales Romero ]]>
                </dc:creator>
                <pubDate>Tue, 15 Apr 2025 14:56:10 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2024/05/Cover-Images-freeCodeCamp.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Artículo original:</strong> <a href="https://www.freecodecamp.org/news/building-a-real-time-chat-app-with-reactjs-and-firebase/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Build a Real-time Chat App with ReactJS and Firebase</a>
      </p><p>En este artículo, voy a mostrarte como crear una app de chat en tiempo real usando React.js y Firebase.</p><p>En la app, vamos a permitir que los usuarios se logueen con su cuenta de Google usando el servicio de inicio de sesion de Firebase. También guardaremos y recuperaremos todos los mensajes usando el servicio Firestone de Firebase. </p><h2 id="prerrequisitos"><strong>Prerrequisitos</strong></h2><p>Para este tutorial, deberás tener instalado Node.js en tu sistema, además deberás tener conocimientos intermedios de CSS, JavaScript y ReactJS y conocimientos sobre el uso de la terminal de líneas de comandos. No debes saber utilizar Firebase para hacer el mismo.</p><h2 id="-que-es-firebase"><strong>¿Que es Firebase?</strong></h2><p>Firebase es un <a href="https://es.wikipedia.org/wiki/Backend_as_a_service">BaaS (Backend as a Service)</a>, una plataforma de backend propiedad de Google que permite a desarrolladores construir aplicaciones para iOS, Android o aplicaciones web.</p><p>Nos provee con herramientas para el seguimiento de análisis, reportar y solucionar fallas de aplicaciones y crear pruebas de marketing y productos. Esto, ayuda a los desarrolladores a crear aplicaciones de calidad, hacer crecer su base de usuarios y obtener ganancias.</p><p>Para este tutorial, vamos a utilizar dos herramientas: Firebase Authentication y Cloud Firestone.</p><h3 id="firebase-authentication"><strong><strong><strong>Firebase Authentication</strong></strong></strong></h3><p>Firebase Authentication (SDK) es una herramienta de Firebase que permite realizar autenticación de usuarios mediante varios métodos, como contraseñas, números de telefono, cuentas de Google, Facebook, Twitter, GitHub y mas. En este proyecto usaremos la autenticación a través de cuentas de Google.</p><h3 id="cloud-firestore"><strong><strong><strong>Cloud Firestore</strong></strong></strong></h3><p>Cloud Firestones es un servicio de bases de datos no relacionales que nos permite guardar y sincronizar información. Este servicio guarda la información en documentos como pares de clave-valor, y estos documentos en colecciones.</p><p>Los documentos, a su vez pueden tener sub-colecciones, permitiéndonos anidar colecciones dentro de otras colecciones, además la información se sincroniza automáticamente entre todos los dispositivos que estén consultando esos datos.</p><p>Ahora que ya tienes una idea general de como trabajan Firebase y Cloud Firestone, vamos a trabajar en el proyecto.</p><p><strong><strong>Not</strong>a<strong>:</strong></strong> para este proyecto, ya escribí el CSS y preconstruí los componentes para la aplicación de chat, puedes encontrar el código del proyecto final en <a href="https://github.com/Timonwa/react-chat">GitHub</a> y los componentes junto con el CSS en la carpeta <a href="https://github.com/Timonwa/react-chat/tree/main/setup">"setup"</a>, y también puedes ver el proyecto final funcionando en este <a href="https://react-chat-timonwa.vercel.app/">link</a></p><h2 id="como-crear-nuestra-aplicaci-n-de-react"><strong>Como crear nuestra aplicación de React</strong></h2><p>Clona este repositorio de <a href="https://github.com/Timonwa/react-chat">GitHub</a>, borra la carpeta src en el directorio raíz y remplázala con la carpeta "src" que se encuentra en la carpeta <a href="https://github.com/Timonwa/react-chat/tree/main/setup">"setup"</a>.</p><p>Alternativamente, puedes crear tu aplicación de React corriendo el comando create-react-app en la terminal.</p><pre><code class="language-node">npx create-react-app react-chat</code></pre><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/06/install-create-react-app.jpg" class="kg-image" alt="install-create-react-app" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/06/install-create-react-app.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/2024/06/install-create-react-app.jpg 776w" width="776" height="430" loading="lazy"><figcaption>Comando create-react-app corriendo (en la parte superior se ve el comando de instalación: npm install -g create-react.app)</figcaption></figure><p><code>react-chat</code> &nbsp;es el nombre de la aplicación. Una vez que finaliza correremos el comando &nbsp;<code>npm install firebase react-firebase-hooks</code> para instalar <strong><strong>firebase</strong></strong> y <strong><strong>react-firebase-hooks</strong></strong>.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/06/install-firebase.jpg" class="kg-image" alt="install-firebase" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/06/install-firebase.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/2024/06/install-firebase.jpg 770w" width="770" height="267" loading="lazy"><figcaption>Comando install firebase react-firebase-hooks corriendo (se aprecia que primeramente se cambio al directorio react-chat</figcaption></figure><p>Una vez que finaliza, borra la carpeta <strong><strong>src</strong></strong> y reemplazala con la que descargaste de la <a href="https://github.com/Timonwa/react-chat/tree/main/setup">carpeta setup</a> para usar el archivo CSS y los componentes preconstruidos. (Opcionalmente, puedes escribir tus propios componentes y tu propio archivo CSS)</p><p>Ahora, tu carpeta <strong><strong>src</strong></strong> contiene los siguientes componentes:</p><ul><li>Una carpeta de componentes con un componente <strong><strong>NavBar</strong></strong> que contiene los botones de <strong><strong>Google sign-in</strong></strong> y <strong><strong>Sign Out</strong>,</strong></li><li>Un componente que le da la <strong>Bienvenida</strong> al usuario que no está logueado,</li><li>Un componente <strong><strong>Chatbox</strong></strong> que solo es visible cuando el usuario está logueado.</li><li>El componente <strong><strong>Message</strong></strong> para mostrar los mensajes de los usuarios,y</li><li>El componente <strong><strong>SendMessage</strong></strong> que usaremos para que el usuario pueda escribir y enviar sus mensajes.</li></ul><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/componentes-en-vs-code.jpg" class="kg-image" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667917866994_image2.png" width="146" height="217" loading="lazy"></figure><p>Además, tenemos lo siguiente:</p><ul><li>Una carpeta <strong><strong>img</strong></strong> donde se guarda la imagen de Google sign-in y sign-out,</li><li>un archivo CSS llamado <strong><strong>App.css</strong></strong> con el código CSS para la aplicación,</li><li>un archivo llamado <strong><strong>App.js</strong></strong> con todos los componentes importados dentro,</li><li>y un archivo llamado <strong><strong>index.js</strong></strong>.</li></ul><p>Corre el comando <code>npm start</code> para ver la aplicación en el navegador, deberías ver algo como esto:</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/react-chat-running.jpg" class="kg-image" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667916861885_image1.png" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/react-chat-running.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/react-chat-running.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/react-chat-running.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/react-chat-running.jpg 1916w" sizes="(min-width: 1200px) 1200px" width="1916" height="920" loading="lazy"><figcaption>Nuestra app de React corriendo en Local</figcaption></figure><p>Ahora, creemos una cuenta de Firebase y configuremos nuestro proyecto.</p><h2 id="como-configurar-el-proyecto-en-firebase"><strong>Como configurar el proyecto en Firebase</strong></h2><p>Si todavía no tienes una cuenta de <a href="https://firebase.google.com/">Firebase</a>, puedes abrir una usando tu correo de Gmail (solo se puede usar el <a href="https://mail.google.com/mail">mail de Google</a>). </p><p>En la página de inicio, haga clic en <strong>comenzar </strong>y luego en crear un proyecto.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-web.jpg" class="kg-image" alt="firebase-web" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firebase-web.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firebase-web.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/firebase-web.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-web.jpg 1903w" sizes="(min-width: 1200px) 1200px" width="1903" height="924" loading="lazy"></figure><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-web-crear.jpg" class="kg-image" alt="firebase-web-crear" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firebase-web-crear.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firebase-web-crear.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/firebase-web-crear.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-web-crear.jpg 1920w" sizes="(min-width: 1200px) 1200px" width="1920" height="918" loading="lazy"></figure><p>Completa el formulario para crear el proyecto agregando un nombre de proyecto (en nuestro caso usaremos el nombre React-chat). Si quieres dejar habilitado Google Analytics para tu proyecto déjalo, sino deshabilítalo. Después, haz clic en crear proyecto</p><p></p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-paso1.jpg" class="kg-image" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667930368482_image5.png" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firebase-paso1.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firebase-paso1.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-paso1.jpg 1177w" width="1177" height="739" loading="lazy"><figcaption>Creando el proyecto paso 1</figcaption></figure><p>Una vez creado, presiona el botón <strong><strong>Continu</strong>ar</strong>.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-pasoFinal.jpg" class="kg-image" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667930783189_image8.png" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firebase-pasoFinal.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firebase-pasoFinal.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/firebase-pasoFinal.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-pasoFinal.jpg 1832w" sizes="(min-width: 1200px) 1200px" width="1832" height="906" loading="lazy"><figcaption>Lo que verás mientras Firebase crea tu proyecto.</figcaption></figure><p>Elige el tipo de aplicación al que quieres agregar Firebase, para este artículo elegiremos el icono de código porque estamos desarrollando una aplicación web. </p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-final-1.jpg" class="kg-image" alt="firebase-final-1" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firebase-final-1.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firebase-final-1.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/firebase-final-1.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-final-1.jpg 1920w" sizes="(min-width: 1200px) 1200px" width="1920" height="913" loading="lazy"><figcaption>Elige el tipo de aplicación al que quieres integrar Firebase</figcaption></figure><p></p><p>Ingresa un sobrenombre para la aplicación y haz clic en el botón <strong>registrar app</strong>.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-registrar-app.jpg" class="kg-image" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667934485512_image10.png" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firebase-registrar-app.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firebase-registrar-app.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/firebase-registrar-app.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-registrar-app.jpg 1916w" sizes="(min-width: 1200px) 1200px" width="1916" height="916" loading="lazy"><figcaption>Registra tu app</figcaption></figure><p>En el menú selecciona npm, copia el código (lo vamos a utilizar más tarde) y haz clic en <strong>ir a la consola</strong>. </p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-sdk.jpg" class="kg-image" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667934988668_image11.png" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firebase-sdk.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firebase-sdk.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/firebase-sdk.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-sdk.jpg 1920w" sizes="(min-width: 1200px) 1200px" width="1920" height="918" loading="lazy"><figcaption>Código para el SDK de Firebase</figcaption></figure><h2 id="c-mo-configurar-firebase-authentication"><strong>Cómo configurar Firebase Authentication</strong></h2><p>Para configurar Firebase Authentication, ve al menú de la izquierda y presiona sobre <strong>Compilación </strong>y selecciona <strong>Aut<strong>hentication</strong></strong> del desplegable.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-autentication.jpg" class="kg-image" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667937032772_image12.png" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firebase-autentication.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firebase-autentication.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/firebase-autentication.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-autentication.jpg 1920w" sizes="(min-width: 1200px) 1200px" width="1920" height="918" loading="lazy"><figcaption>Selecciona Authentication del menú desplegable.</figcaption></figure><p>Haz clic en comenzar y selecciona a Google en el menú proveedores de acceso.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-autentication-conf.jpg" class="kg-image" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667937105063_image13.png" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firebase-autentication-conf.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firebase-autentication-conf.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/firebase-autentication-conf.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-autentication-conf.jpg 1920w" sizes="(min-width: 1200px) 1200px" width="1920" height="920" loading="lazy"><figcaption>Elige el método de autenticación.</figcaption></figure><p>Presiona sobre el botón de la derecha para habilitar el proyecto y selecciona un correo de soporte, luego haz clic en guardar.</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-autentication-conf-mail.jpg" class="kg-image" alt="https://paper-attachments.dropboxusercontent.com/s_B6DDEC735898D3445BBF655B53FFE42B53361DCD6229DE6836CD5302F930DF9D_1667937310260_image14.png" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firebase-autentication-conf-mail.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firebase-autentication-conf-mail.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/firebase-autentication-conf-mail.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-autentication-conf-mail.jpg 1920w" sizes="(min-width: 1200px) 1200px" width="1920" height="921" loading="lazy"></figure><h2 id="c-mo-configurar-cloud-firestone"><strong>Cómo configurar Cloud Firestone</strong></h2><p>Nuevamente. ve al menú de la izquierda, clickea en <strong>compilación</strong> y selecciona <strong>Firestone</strong>. Luego haz clic en crear base de datos y completa el formulario.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firestone-db.jpg" class="kg-image" alt="image9-2" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firestone-db.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firestone-db.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/firestone-db.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firestone-db.jpg 1920w" sizes="(min-width: 1200px) 1200px" width="1920" height="921" loading="lazy"><figcaption>Configurando Cloud Firestore</figcaption></figure><p>En el primer paso, selecciona la localización de la base de datos, por defecto el sistema asignará la ubicación más cercana a la que te encuentras, luego presiona siguiente. </p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firestone-db-seteo1.jpg" class="kg-image" alt="firestone-db-seteo1" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firestone-db-seteo1.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firestone-db-seteo1.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/firestone-db-seteo1.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firestone-db-seteo1.jpg 1920w" sizes="(min-width: 1200px) 1200px" width="1920" height="916" loading="lazy"><figcaption>Setea la localización de la base de datos y presiona siguiente.</figcaption></figure><p>Selecciona el modo de la base de datos, puedes elegir producción o modo de prueba.</p><p>El modo de prueba permite que cualquier cliente puede leer o escribir en la base de datos por 30 días. El modo producción significa que nadie puede leer o escribir en la base de datos, por lo que debes escribir tus reglas para brindar acceso a la base de datos.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firestone-db-seteo2.jpg" class="kg-image" alt="image10-2" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firestone-db-seteo2.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firestone-db-seteo2.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/firestone-db-seteo2.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firestone-db-seteo2.jpg 1920w" sizes="(min-width: 1200px) 1200px" width="1920" height="920" loading="lazy"><figcaption>Elige el modo de producción o el modo de pruebas (nosotros elegiremos el modo de producción).</figcaption></figure><p>Selecciona <strong>modo de producción</strong> y presiona el botón <strong>crear</strong>.</p><p>El siguiente paso será editar nuestras reglas, haz clic en la pestaña <strong>reglas</strong>.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-reglas-1.jpg" class="kg-image" alt="image12-2" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firebase-reglas-1.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firebase-reglas-1.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/firebase-reglas-1.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-reglas-1.jpg 1920w" sizes="(min-width: 1200px) 1200px" width="1920" height="899" loading="lazy"><figcaption>Edita las reglas ingresando en la pestaña Reglas.</figcaption></figure><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-reglas-2.jpg" class="kg-image" alt="image13-2" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/firebase-reglas-2.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/firebase-reglas-2.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/size/w1600/2024/07/firebase-reglas-2.jpg 1600w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/firebase-reglas-2.jpg 1920w" sizes="(min-width: 1200px) 1200px" width="1920" height="918" loading="lazy"><figcaption>Reemplaza esta regla.</figcaption></figure><p>Reemplaza la regla que esta en la pestaña por la siguiente y presiona el boton <strong>publicar</strong>.</p><pre><code class="language-js">rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read: if true;
      allow create, update, delete, write: if request.auth != null;
    }
  }
}</code></pre><p>Analicemos el código, la primer parte, &nbsp;<code>allow read: if true;</code> significa que cualquiera puede leer tu base de datos, mientras que el bloque de código siguiente <code>allow create, update, delete, write: if request.auth != null;</code> significa que solo los clientes autenticados pueden crear, actualizar, borrar y escribir datos en la base de datos.</p><p>Puedes comenzar agregando o creando una colección en tu base de datos o crear una automáticamente en la aplicación que estamos desarrollando, lo que haremos después. Si quieres crear una colección en Cloud Firestone, vuelve a la pestaña de <strong>datos </strong>presionando en la misma y presiona el botón <strong>iniciar coleccion</strong>.</p><p>Ingresa el nombre de la colección, por ejemplo "messages" y clickea en <strong>siguiente</strong>.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/coleecion-ejemplo.jpg" class="kg-image" alt="image14-2" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/coleecion-ejemplo.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/coleecion-ejemplo.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/coleecion-ejemplo.jpg 1099w" width="1099" height="626" loading="lazy"><figcaption>Iniciando una colección en Firestone.</figcaption></figure><p>Ahora, crea el documento para la colección, </p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/07/coleecion-ejemplo-2.jpg" class="kg-image" alt="image15-1" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/07/coleecion-ejemplo-2.jpg 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2024/07/coleecion-ejemplo-2.jpg 1000w, https://www.freecodecamp.org/espanol/news/content/images/2024/07/coleecion-ejemplo-2.jpg 1117w" width="1117" height="664" loading="lazy"><figcaption>Creando un documento para la colección.</figcaption></figure><p>Clickea en <strong>ID automático</strong>, de esa forma el sistema generará el ID de forma automática, o si lo prefieres ingresa uno tu.</p><p>Luego, se crea el par clave-&gt;valor para el documento. El campo llamado "Campo" representa el nombre de la clave, el campo "Tipo" nos dice que valor almacena (cadena de texto, number, timeStamp, etc), y finalmente el campo "Valor" es lo que guardamos en esta clave.</p><p>Puedes seguir agregando más documentos presionando en el enlace "Agregar campo", sino presiona en Guardar para que se guarde la colección.</p><p>Nuestro proyecto de Firebase ya esta correctamente seteado, volvamos a la aplicación de React.</p><h2 id="c-mo-configurar-firebase-en-react"><strong>Cómo configurar Firebase en React</strong></h2><p>En la carpeta <strong><strong>src</strong></strong>, crea un archivo llamado <code>firebase.js</code> y pega el código que copiamos al configurar firebase.</p><p>Tambien importemos los servicios <code>getAuth</code> &nbsp;y <code>getFirestore</code> desde <a href="https://firebase.google.com/docs/auth/web/start?hl=es">Autenticación para Web</a> y <a href="https://firebase.google.com/docs/firestore/quickstart?hl=es">Firestore en la nube para web</a> respectivamente. Puedes leer más sobre las bibliotecas disponibles para Firebase en la página de la <a href="https://firebase.google.com/docs/web/setup?hl=es#available-libraries">documentación</a>.</p><p>Nuestro archivo firebase.js debe verse asi:</p><pre><code class="language-js">// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: LA_CLAVE_PARA_LA_APLICACION_VA_AQUI(CLAVE_API),
  authDomain: EL_DOMINIO_DE_AUTENTICACION_PARA_LA_APP_VA_AQUI,
  projectId: EL_ID_DEL_PROYECTO_PARA_LA_APP_VA_AQUI,
  storageBucket: EL_BUCKET_PARA_LA_APLICACION_VA_AQUI,
  messagingSenderId: EL_ID_PARA_ENVIAR_LOS_MENSAJES_VA_AQUI,
  appId: EL_APP_ID_DE_LA_APLICACION_DE_REACT_VA_AQUI,
};</code></pre><h2 id="c-mo-implementar-firebase-dentro-de-nuestra-aplicaci-n-react"><strong>Cómo implementar Firebase dentro de nuestra aplicación React</strong></h2><h3 id="c-mo-autenticar-usuarios-con-su-cuenta-de-google"><strong>Cómo autenticar usuarios con su cuenta de Google</strong></h3><p>Nosotros queremos que los usuarios tengan acceso a la aplicación de chat y que en caso de que estén autenticados puedan enviar mensajes, si no lo están queremos que vean la página de bienvenida para poder loguearse usando el botón de Google sign-in, y si esta logueado debe poder ver el botón de Sign-out para salir del chat.</p><p>Esta autenticación será controlada por el componente NavBar, que contiene ambos botones (sign-in y sign-out).</p><p>En nuestro componente <code>NavBar</code>, importaremos nuestra imagen de Google "sign-in", y la guardaremos como la constante llamada <code>GoogleSignin</code>. También tendremos un estado llamado <code>user</code>, seteado como falso, una función llamada <code>googleSignIn</code> que setea el estado de <code>user</code> como verdadero, y una función llamada <code>signOut</code> que setea el estado de <code>user</code> como falso. </p><p>También tenemos un elemento <code>nav</code> con un tag <code>h1</code> que representa el título de nuestra app y dos botones que se renderizan condicionalmente en base al estado del componente <code>user</code></p><pre><code class="language-js">import React, { useState } from "react";
import GoogleSignin from "../img/btn_google_signin_dark_pressed_web.png";

const NavBar = () =&gt; {
  const [user, setUser] = useState(false);
  const googleSignIn = () =&gt; {
    setUser(true);
  };
  const signOut = () =&gt; {
    setUser(false);
  };
  return (
    &lt;nav className="nav-bar"&gt;
      &lt;h1&gt;React Chat&lt;/h1&gt;
      {user ? (
        &lt;button onClick={signOut} className="sign-out" type="button"&gt;
          Sign Out
        &lt;/button&gt;
      ) : (
        &lt;button className="sign-in"&gt;
          &lt;img
            onClick={googleSignIn}
            src={GoogleSignin}
            alt="sign in with google"
            type="button"
          /&gt;
        &lt;/button&gt;
      )}
    &lt;/nav&gt;
  );
};
export default NavBar;</code></pre><p>Hagamos un cambio en el componente <strong>NavBar,</strong> importando lo siguiente:</p><pre><code class="language-js">import { auth } from "../firebase";
import { useAuthState } from "react-firebase-hooks/auth";
import { GoogleAuthProvider, signInWithRedirect } from "firebase/auth";</code></pre><p>Reemplaza el estado del usuario con el siguiente código:</p><p><code>const [user] = useAuthState(auth);</code></p><p>Y edita las funciones googleSignIn y signOut:</p><pre><code class="language-js">const googleSignIn = () =&gt; {
  const provider = new GoogleAuthProvider();
  signInWithRedirect(auth, provider);
};
const signOut = () =&gt; {
  auth.signOut();
};</code></pre><p>La función <code>useAuthState</code> se dispara en el momento que el usuario se loguea o desloguea, permitiéndonos acceder a los detalles del usuario. Actualmente, el estado del usuario es <code>null</code>, una vez que te logueas, el estado del usuario cambiará a la información suministrada por el método de autenticación (en este caso, Google).</p><p>En la función <code>googleSignIn</code>, le indicamos a Firebase que el el usuario quiere autenticarse con Google usando el <code>GoogleAuthProvider()</code>. También lo redirige a la página de inicio de sesión de Google.</p><p>Luego de el correcto inicio de sesión del usuario, sus datos son almacenados en <code>auth</code>, y el usuario es redireccionado a la app. La función <code>signOut</code> borra la información almacenada de autenticación, retornando ésta a <code>null</code>. El nuevo estado almacenado también determina qué botón debe renderizarse.</p><p>Agreguemos también autenticación a nuestro archivo <strong>App.js</strong>. Importa lo siguiente:</p><pre><code class="language-js">import { auth } from "./firebase";
import { useAuthState } from "react-firebase-hooks/auth";</code></pre><p>Agrega el nuevo user state, para que podamos renderizar el componente <strong>Welcome</strong> si el usuario no está logueado o el componente <strong>Chatbox</strong> si el usuario ya se autenticó.</p><pre><code class="language-js">const [user] = useAuthState(auth);</code></pre><p>El codigo final debe lucir así:</p><pre><code class="language-js">import { auth } from "./firebase";
import { useAuthState } from "react-firebase-hooks/auth";
import "./App.css";
import NavBar from "./components/NavBar";
import ChatBox from "./components/ChatBox";
import Welcome from "./components/Welcome";

function App() {
  const [user] = useAuthState(auth);
  return (
    &lt;div className="App"&gt;
      &lt;NavBar /&gt;
      {!user ? &lt;Welcome /&gt; : &lt;ChatBox /&gt;}
    &lt;/div&gt;
  );
}
export default App;</code></pre><p>Al probar nuestras nuevas funciones de sign-in y sign-out, veremos lo siguiente:</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2023/01/ezgif-4-b6465d1647-1.gif" class="kg-image" alt="ezgif-4-b6465d1647-1" width="600" height="400" loading="lazy"><figcaption>App demo</figcaption></figure><p>Ahora, hagamos lo mismo con el componente <strong>Welcome, </strong>que ahora tiene el siguiente código:</p><pre><code class="language-js">import React from "react";
import GoogleSignin from "../img/btn_google_signin_dark_pressed_web.png";

const Welcome = () =&gt; {
  const googleSignIn = () =&gt; {};

  return (
    &lt;main className="welcome"&gt;
      &lt;h2&gt;Welcome to React Chat.&lt;/h2&gt;
      &lt;img src="/logo512.png" alt="ReactJs logo" width={50} height={50} /&gt;
      &lt;p&gt;Sign in with Google to chat with with your fellow React Developers.&lt;/p&gt;
      &lt;button className="sign-in"&gt;
        &lt;img
          onClick={googleSignIn}
          src={GoogleSignin}
          alt="sign in with google"
          type="button"
        /&gt;
      &lt;/button&gt;
    &lt;/main&gt;
  );
};
export default Welcome;</code></pre><p>Importaremos lo siguiente:</p><pre><code class="language-js">import { auth } from "../firebase";
import { GoogleAuthProvider, signInWithRedirect } from "firebase/auth";</code></pre><p>y tambien editaremos la funcion googleSignIn:</p><pre><code class="language-js">const googleSignIn = () =&gt; {
    const provider = new GoogleAuthProvider();
    signInWithRedirect(auth, provider);
};</code></pre><p>Ahora, podremos loguearnos desde el botón que se renderiza en el componente <strong>Welcome</strong>:</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2023/01/video2-1.gif" class="kg-image" alt="video2-1" width="600" height="400" loading="lazy"><figcaption>Updated app demo</figcaption></figure><h3 id="como-enviar-y-almacenar-mensajes-en-firebase-"><strong>Como enviar y almacenar mensajes en Firebase:</strong></h3><p>Actualmente, estamos mostrando un mensaje de prueba de nuestro componente <strong>Message y, </strong>el botón<strong> <strong>Send</strong></strong> no realiza ninguna acción. Cuando ingresamos un mensaje y pulsamos el botón <strong><strong>Send</strong></strong>, queremos que el mensaje se envíe de inmediato a la aplicación.</p><p>Así, que editemos el componente <strong><strong>SendMessage</strong>:</strong> </p><p>Primero, importamos <code>useState</code> de React, <code>auth</code> y <code>db</code> &nbsp;de nuestro archivo de configuración de firebase, y <code>addDoc</code>, <code>collection</code> y <code>serverTimestamp</code> de la librería Firestore.</p><pre><code class="language-js">import React, { useState } from "react";
import { auth, db } from "../firebase";
import { addDoc, collection, serverTimestamp } from "firebase/firestore";</code></pre><p>Crearemos un estado llamado <code>message</code> que inicialmente esté definido como una cadena vacía y se pase como valor a la etiqueta <code>input</code>. La funcion <code>onChange</code> es también agregada a el input, que setea el estado del <code>message</code> en lo que el usuario tipee.</p><pre><code class="language-js">const SendMessage = () =&gt; {
  const [message, setMessage] = useState("");
    
  return (
    &lt;form className="send-message"&gt;
      &lt;input
        ...
        value={message}
        onChange={(e) =&gt; setMessage(e.target.value)}
      /&gt;
      &lt;button type="submit"&gt;Send&lt;/button&gt;
    &lt;/form&gt;
  );
};</code></pre><p>También creamos una función llamada <code>sendMessage</code>, y agregamos el atributo <code>onSubmit</code> en nuestro formulario, que ejecuta la función <code>sendMessage</code> cuando el usuario clickea en el botón <code>Send</code> (enviar). Nótese que el botón debe tener el tipo <code>type="submit"</code> &nbsp;para que la función funcione.</p><pre><code class="language-js"> const sendMessage = async (event) =&gt; {
    event.preventDefault();
    if (message.trim() === "") {
      alert("Enter valid message");
      return;
    }
    const { uid, displayName, photoURL } = auth.currentUser;
    await addDoc(collection(db, "messages"), {
      text: message,
      name: displayName,
      avatar: photoURL,
      createdAt: serverTimestamp(),
      uid,
    });
    setMessage("");
  };
  
  return (
    &lt;form onSubmit={(event) =&gt; sendMessage(event)} className="send-message"&gt;
...</code></pre><p>La función <code>sendMessage</code> es una funcion asincrona. Primero chequea si el el usuario esta intentando enviar una cadena vacía o espacios vacíos como un mensaje y, en caso de que así sea alerta al usuario. </p><p>Si el mensaje no es una cadena vacía, toma del usuario los siguientes datos dela información proporcionada por <code>auth</code> al logguearse: <strong>uid, displayName </strong>y <strong>photoURL.</strong> Estos datos corresponden con el identificador único del usuario, su nombre y la URL de la foto del usuario, respectivamente.</p><p>Luego, usa la función <code>addDoc()</code> para crear un <strong>documento</strong> dentro de la <strong>colección</strong> <strong>messages</strong> de nuestra <strong>base de datos, </strong>a la que accede gracias al archivo que importamos. En caso de que la <strong>colección</strong> no exista, crea una por nosotros. </p><p>También crea un par de <strong>llave-valor</strong>, guardando nuestro <strong>mensaje</strong> en <strong>text, displayName </strong>en <strong>name, </strong>guardando el momento en el que el mensaje fue guardado en nuestra base de datos en la columna <strong>createAt</strong>, y el <strong>uid</strong>.</p><p>Estos pares de llave-valor, conforman los datos de nuestro documento. Una vez que se completa la acción, se restablece el estado del mensajea una cadena vacía.</p><h3 id="como-recuperar-mensajes-de-nuestra-base-de-datos"><strong>Como recuperar mensajes de nuestra base de datos</strong></h3><p>Luego de enviar el mensaje del usuario, debemos mostrarlo en la pantalla. Vayamos a nuestro componente <strong>chatBox</strong> e importemos lo siguiente:</p><pre><code class="language-js">import { useEffect, useRef, useState } from "react";
import {
  query,
  collection,
  orderBy,
  onSnapshot,
  limit,
} from "firebase/firestore";
import { db } from "../firebase";</code></pre><p>Creamos el hook <code>useEffect</code> que correra cada vez que se realizan cambios en el thatroom, por ejemplo enviar o borrar un mensaje.</p><pre><code class="language-js">useEffect(() =&gt; {
  const q = query(
    collection(db, "messages"),
    orderBy("createdAt", "desc"),
    limit(50)
  );
  const unsubscribe = onSnapshot(q, (QuerySnapshot) =&gt; {
    const fetchedMessages = [];
    QuerySnapshot.forEach((doc) =&gt; {
      fetchedMessages.push({ ...doc.data(), id: doc.id });
    });
    const sortedMessages = fetchedMessages.sort(
      (a, b) =&gt; a.createdAt - b.createdAt
    );
    setMessages(sortedMessages);
  });
  return () =&gt; unsubscribe;
}, []);</code></pre><p>En el hook <code>useEffect</code>, tenemos la constante <code>q</code>, una consulta de Firebase que busca los mensajes dentro de la base de la colección <strong>messages</strong> de nuestra base de datos, luego ordena los documentos obtenidos basados en la llave <strong>createAt </strong>y devuelve un máximo de <strong>50</strong> documentos (mensajes guardados)</p><p>La constante <code>unsubscribe</code> representa la función <code>onSnapshot</code>, que escucha los cambios en el documento, esta función tiene un arreglo vacío llamado <code>messages</code>.</p><p>El bucle <code>forEach</code> itera entre todos los <strong>documentos</strong> de la <strong>colección</strong> y guarda la información en un nuevo arreglo. Luego, asigna el arreglo inicial de mensajes al nuevo arreglo de mensajes.</p><p>También usamos el <strong>método map</strong> en nuestro arreglo de mensajes para &nbsp;cada mensaje/documento en nuestro componente <strong>message</strong>.</p><pre><code class="language-js">{messages?.map((message) =&gt; (
  &lt;Message key={message.id} message={message} /&gt;
))}</code></pre><p>El codigo completo luce asi:</p><pre><code class="language-js">import React, { useEffect, useRef, useState } from "react";
import {
  query,
  collection,
  orderBy,
  onSnapshot,
  limit,
} from "firebase/firestore";
import { db } from "../firebase";
import Message from "./Message";
import SendMessage from "./SendMessage";

const ChatBox = () =&gt; {
  const [messages, setMessages] = useState([]);
  const scroll = useRef();

  useEffect(() =&gt; {
    const q = query(
      collection(db, "messages"),
      orderBy("createdAt", "desc"),
      limit(50)
    );

    const unsubscribe = onSnapshot(q, (QuerySnapshot) =&gt; {
      const fetchedMessages = [];
      QuerySnapshot.forEach((doc) =&gt; {
        fetchedMessages.push({ ...doc.data(), id: doc.id });
      });
      const sortedMessages = fetchedMessages.sort(
        (a, b) =&gt; a.createdAt - b.createdAt
      );
      setMessages(sortedMessages);
    });
    return () =&gt; unsubscribe;
  }, []);

  return (
    &lt;main className="chat-box"&gt;
      &lt;div className="messages-wrapper"&gt;
        {messages?.map((message) =&gt; (
          &lt;Message key={message.id} message={message} /&gt;
        ))}
      &lt;/div&gt;
      {/* when a new message enters the chat, the screen scrolls down to the scroll div */}
      &lt;span ref={scroll}&gt;&lt;/span&gt;
      &lt;SendMessage scroll={scroll} /&gt;
    &lt;/main&gt;
  );
};

export default ChatBox;</code></pre><p>Vayamos a nuestro componente <strong>message</strong>, y rendericemos los datos recibidos en el navegador.</p><pre><code class="language-js">import React from "react";
import { auth } from "../firebase";
import { useAuthState } from "react-firebase-hooks/auth";
const Message = ({ message }) =&gt; {
  const [user] = useAuthState(auth);

  return (
    &lt;div
      className={`chat-bubble ${message.uid === user.uid ? "right" : ""}`}&gt;
      &lt;img
        className="chat-bubble__left"
        src={message.avatar}
        alt="user avatar"
      /&gt;
      &lt;div className="chat-bubble__right"&gt;
        &lt;p className="user-name"&gt;{message.name}&lt;/p&gt;
        &lt;p className="user-message"&gt;{message.text}&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};
export default Message;
</code></pre><p>Importamos <code>auth</code> y <code>useAuthState</code>, y almacenamos los datos del usuario en &nbsp;<code>user</code>. Deconstruimos el <code>prop</code> <strong>message</strong> y asignamos el avatar al atributo <code>src</code> del componente img. También reemplazamos el nombre y el mensaje "de prueba" con el mensaje obtenido desde <strong>message</strong>. </p><p>También aplicamos una condición al estilo CSS según el <code>uid</code> del autor del mensaje, entonces si el <code>uid</code> del autor del mensaje es el mismo que el del usuario logueado, se aplica al div el estilo definido en el selector <strong>derecho</strong>; en caso contrario no se agrega ningún estilo adicional.</p><p>Actualmente, todos &nbsp;los mensajes se posicionan a la izquierda, así que si el autor del mensaje es el usuario logueado, sus mensajes se posicionaran a la derecha. Veamos esa dinámica en acción en el navegador:</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2023/01/video3-1.gif" class="kg-image" alt="video3-1" width="600" height="400" loading="lazy"><figcaption>Demostración de la app actualizada</figcaption></figure><p>El mensaje es enviado y almacenado en nuestra base de datos, luego todos los mensajes se recuperan y la sala es actualizada en tiempo real con los mensajes nuevos.</p><p>El nombre y el avatar de cada usuario son &nbsp;mostrados en cada tarjeta, pero también podemos ver que el chat no se desliza automáticamente hacia abajo cuando llega un nuevo mensaje. Vamos a arreglar eso.</p><h3 id="como-hacer-que-el-chat-se-desplace-hasta-el-final"><strong>Como hacer que el chat se desplace hasta el final</strong></h3><p>Vayamos al archivo <strong>ChatBox.js</strong>, importemos el hook <code>useRef</code> y creemos una constante llamada scroll:</p><pre><code class="language-js">import React, { useEffect, useRef, useState } from "react";
...
const scroll = useRef();</code></pre><p>Crearemos un elemento <code>span</code> con un atributo <code>ref</code> conectado al valor de <strong>scroll</strong>,y , también pasamos el &nbsp;<strong>s<strong>crol</strong>l </strong>en nuestro componente<strong> message.</strong></p><p>Luego, creamos un elemento <code>span</code> con un atributo <code>ref</code> cuyo valor es <strong>scroll</strong>, y también pasamos <strong>scroll</strong> a nuestro componente <code>SendMessage</code>:</p><pre><code class="language-js">&lt;main className="chat-box"&gt;
   ...
   {/* when a new message enters the chat, the screen scrolls dowwn to the scroll div */}
   &lt;span ref={scroll}&gt;&lt;/span&gt;
   &lt;SendMessage scroll={scroll} /&gt;
&lt;/main&gt;</code></pre><p>Luego vamos al componente <strong>Messages</strong>, accedemos a la constante scroll, y agregamos <code>scroll.current.scrollIntoView({ behavior: "smooth" })</code> al final de nuestra función <code>sendMessage</code>.</p><p>Este código le indica al navegador que el scroll span debe ser visible después de enviar un mensaje. Por eso ponemos la etiqueta <code>span</code> al final de cada mensaje.</p><pre><code class="language-js">const SendMessage = ({ scroll }) =&gt; {

  const sendMessage = async (event) =&gt; {
   ...
    setMessage("");
    scroll.current.scrollIntoView({ behavior: "smooth" });
  };
  ...
};</code></pre><p>Volviendo al navegador, deberíamos ver que el chat se desplaza automáticamente hasta el final cuando del usuario envía un mensaje.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2023/01/video4-1.gif" class="kg-image" alt="video4-1" width="600" height="400" loading="lazy"><figcaption>Demo mostrando como el chat se desliza hasta el final con un nuevo mensaje.</figcaption></figure><h2 id="como-agregar-dominios-autorizados"><strong>Como agregar dominios autorizados</strong></h2><p>Cuando desplegamos nuestra aplicacion en React, es indispensable agregar nuestro dominio a la lista de dominios autorizados en Firebase. Este paso, asegura que nuestra app se comunique correctamente con los servicios de Firebase, veamos como hacerlo:</p><p>En la consola de Firebase, navega hasta la sección <strong>Autenticación </strong>y haz click en la pestaña de <strong>configuración.</strong> Deslízate hasta la parte de <strong>Dominios autorizados; </strong>a continuación clickea en el botón<strong> agregar un dominio. </strong>Luego agrega el o los dominios donde la aplicación será desplegada. </p><p>Por ejemplo, si desplegaras la aplicación en la dirección <a href="https://my-react-chat-app.com/" rel="noreferrer nofollow noopener">https://my-react-chat-app.com</a>, ingresa <code>my-react-chat-app.com</code> como un dominio autorizado, y clickea el boton <strong>agregar</strong> para guardar los cambios.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/espanol/news/content/images/2025/03/firestone-agregar-dominio-1.png" class="kg-image" alt="firestone-agregar-dominio-1" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2025/03/firestone-agregar-dominio-1.png 600w, https://www.freecodecamp.org/espanol/news/content/images/size/w1000/2025/03/firestone-agregar-dominio-1.png 1000w, https://www.freecodecamp.org/espanol/news/content/images/2025/03/firestone-agregar-dominio-1.png 1366w" sizes="(min-width: 720px) 720px" width="1366" height="611" loading="lazy"></figure><p>Agregando el dominio donde desplegaras tu app a la lista de dominios autorizados, le brindas permisos a los servicios de Firestone para ser accedidos desde ese dominio. Si no hiciéramos esto, encontraríamos errores al intentar establecer conexión o realizar operaciones con Firebase.</p><h2 id="para-terminar"><strong>Para terminar</strong></h2><p>Y, así es como llegamos al final de la construcción de esta aplicación de chat en tiempo real, ¡felicitaciones!.</p><p>En este tutorial, aprendimos a usar Firebase y React para crear una aplicacion de &nbsp;chat en tiempo real. También autenticamos a los usuarios usando el método de logueo por cuenta de Google de Firetone y, guardamos los mensajes del chat usando Cloud Firestore. Finalmente, aprendimos a usar algunos servicios y librerías de Firestone.</p><p>Puedes encontrar el código de este proyecto en <a href="https://github.com/Timonwa/react-chat">GitHub</a> y puedes probar la aplicación desplegada usando el siguiente <a href="https://react-chat-timonwa.vercel.app/">enlace</a>.</p><p>Si disfrutaste este articulo, por favor compártelo para ayudar a otros desarrolladores, también puedes visitar mi <a href="https://blog.timonwa.com/">blog</a> para leer mas artículos o puedes seguirme en &nbsp;<a href="https://twitter.com/timonwa_">Twitter</a> o <a href="https://www.linkedin.com/in/timonwa/">LinkedIn</a>.</p><p>Hasta la próxima, byeeeeee!</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/espanol/news/content/images/2025/03/HoDL1vbXj-1-1-.gif" class="kg-image" alt="HoDL1vbXj-1-1-" width="498" height="252" loading="lazy"></figure> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Cómo crear y enviar Plantillas de Email usando React Email y Resend en Next.js ]]>
                </title>
                <description>
                    <![CDATA[ Las aplicaciones modernas de Software frecuentemente se basan en comunicación por email para interactuar con los usuarios. Podrían enviar códigos de autenticación durante intentos de inicio de sesión, emails de marketing, o boletines informativos, por ejemplo. Esto significa que las notificaciones de email son típicamente el medio más común de ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/como-crear-y-enviar-plantillas-de-email-usando-react-email-y-resend-en-next-js/</link>
                <guid isPermaLink="false">67cf85b759617604dd901b87</guid>
                
                    <category>
                        <![CDATA[ next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ NextJS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ resend ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Elias Ezequiel Pereyra Gomez ]]>
                </dc:creator>
                <pubDate>Tue, 15 Apr 2025 14:26:02 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2025/03/3450a9b3-e740-4362-b0ab-0269646e725c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Las aplicaciones modernas de Software frecuentemente se basan en comunicación por email para interactuar con los usuarios. Podrían enviar códigos de autenticación durante intentos de inicio de sesión, emails de marketing, o boletines informativos, por ejemplo. Esto significa que las notificaciones de email son típicamente el medio más común de comunicación con los usuarios.</p><p>En este tutorial, aprenderás cómo diseñar plantillas asombrosas de email con <a href="https://react.email/docs/introduction">React Email</a> y enviarlos usando <a href="https://resend.com/docs/send-with-nextjs">Resend</a> – una potente plataforma API de correo electrónico. </p><h2 id="pre-requisitos"><strong>Pre-requisitos</strong></h2><p>Para aprovechar lo máximo de este tutorial, deberías tener un entendimiento básico de React o Next.js.</p><p>También haremos uso de las siguientes herramientas:</p><ul><li>React Email: Una librería que te permite crear plantillas de email diseñadas hermosamente usando componentes de React.</li><li>Resend: Una poderosa y sencilla plataforma API para enviar emails desde tus aplicaciones.</li></ul><h2 id="c-mo-construir-la-aplicaci-n-con-next-js"><strong>Cómo construir la Aplicación con Next.js</strong></h2><p>En esta sección, crearás una aplicación sencilla de soporte de cliente. La aplicación incluirá un formulario para usuarios para enviar sus solicitudes, el cual dispara una notificación de email confirmando que un ticket de soporte ha sido creado.</p><p>Para comenzar, primero configuraremos la interfaz de usuario y un endpoint de la API.</p><p>Ejecuta el siguiente comando para crear un nuevo proyecto de Next.js con TypeScript:</p><pre><code class="language-bash">npx create-next-app react-email-resend
</code></pre><p>Actualiza el archivo <code>app/page.tsx</code> para renderizar un formulario que recoge los detalles del cliente, incluyendo su nombre completo, la dirección de email, el asunto del ticket, y un mensaje con detalles describiendo el problema. Cuando el formulario se envía, los datos son registrados a la consola usando la función <code>handleSubmit</code>.</p><pre><code class="language-typescript">"use client";
import support from "@/app/images/support.jpg";
import { useState } from "react";
import Image from "next/image";

export default function Page() {
    //👇🏻 estados de las entradas
    const [name, setName] = useState&lt;string&gt;("");
    const [email, setEmail] = useState&lt;string&gt;("");
    const [subject, setSubject] = useState&lt;string&gt;("");
    const [content, setContent] = useState&lt;string&gt;("");

    const handleSubmit = async (e: React.FormEvent) =&gt; {
        e.preventDefault();
        //👇🏻 registra las entradas del usuario
        console.log({ name, email, subject, content });
    };
return ({/** -- elementos de UI -- */})
}
</code></pre><p>Regresa los elementos UI del formulario que aceptan el nombre completo del usuario, la dirección de email, asunto del ticket, y un mensaje con los detalles describiendo el problema.</p><pre><code class="language-typescript">    return (
        &lt;main className='w-full min-h-screen flex items-center justify-between'&gt;
                &lt;form className='w-full' onSubmit={handleSubmit}&gt;
                    &lt;label htmlFor='name' className='opacity-60'&gt;
                        Full Name
                    &lt;/label&gt;
                    &lt;input
                        type='text'
                        className='w-full px-4 py-3 border-[1px] mb-3 border-gray-300 rounded-sm'
                        id='name'
                        required
                        value={name}
                        onChange={(e) =&gt; setName(e.target.value)}
                    /&gt;

                    &lt;label htmlFor='email' className='opacity-60'&gt;
                        Email Address
                    &lt;/label&gt;
                    &lt;input
                        type='email'
                        className='w-full px-4 py-3 border-[1px] mb-3 border-gray-300 rounded-sm'
                        id='email'
                        value={email}
                        onChange={(e) =&gt; setEmail(e.target.value)}
                        required
                    /&gt;

                    &lt;label htmlFor='subject' className='opacity-60'&gt;
                        Subject
                    &lt;/label&gt;
                    &lt;input
                        type='text'
                        className='w-full px-4 py-3 border-[1px] mb-3 border-gray-300 rounded-sm'
                        id='subject'
                        value={subject}
                        onChange={(e) =&gt; setSubject(e.target.value)}
                        required
                    /&gt;

                    &lt;label htmlFor='message' className='opacity-60'&gt;
                        Message
                    &lt;/label&gt;
                    &lt;textarea
                        rows={7}
                        className='w-full px-4 py-3 border-[1px] mb-3 border-gray-300 rounded-sm'
                        id='message'
                        required
                        value={content}
                        onChange={(e) =&gt; setContent(e.target.value)}
                    /&gt;

                    &lt;button className='w-full bg-blue-500 py-4 px-3 rounded-md font-bold text-blue-50'&gt;
                        SEND MESSAGE
                    &lt;/button&gt;
                &lt;/form&gt;
            &lt;/div&gt;
        &lt;/main&gt;
    );
</code></pre><p>Aquí está la página resultante del componente:</p><figure class="kg-card kg-image-card"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733748196715/703e7e5b-f868-45e6-b62f-64a2d6dd279e.png" class="kg-image" alt="The Page component renders a form that accepts the user's input" width="2560" height="1420" loading="lazy"></figure><p>Luego, crea un endpoint de la API (<code>/api/route.ts</code>) que acepta las entradas del cliente.</p><pre><code class="language-bash">cd app
mkdir api &amp;&amp; cd api
touch route.ts
</code></pre><p>Copia el siguiente código en el archivo <code>api/route.ts</code>. El endpoint de la API registra la entrada del cliente a la consola después de recibirla.</p><pre><code class="language-typescript">import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
    const { name, email, subject, content } = await req.json();
    //👇🏻 registra los contenidos
    console.log({ name, email, subject, content });
    return NextResponse.json({
        message: "Email enviado con éxito",
        data,
 });
}
</code></pre><p>Actualiza la función <code>handleSubmit</code> para enviar los datos del cliente al endpoint de la API y regresa la respuesta JSON:</p><pre><code class="language-typescript">const handleSubmit = async (e: React.FormEvent) =&gt; {
    e.preventDefault();

    try {
        const response = await fetch("/api", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
            },
            body: JSON.stringify({ name, email, subject, content }),
        });
        const data = await response.json();
        alert(data.message);
    } catch (error) {
        console.error(error);
        alert("Un error ocurrió, por favor intenta nuevamente más tarde");
    }
    setName("");
    setEmail("");
    setSubject("");
    setContent("");
};
</code></pre><p>¡Felicidades! Has configurado la colección de datos y el envío. En las próximas secciones, te mostraré cómo crear y enviar plantillas de email con React Email y Resend.</p><h2 id="c-mo-crear-plantillas-de-email-usando-react-email"><strong>Cómo crear Plantillas de Email usando React Email</strong></h2><p>React Email te permite construir y enviar componentes de email usando React y TypeScript. Soporta múltiples clientes de email, incluyendo Gmail, Mail de Yahoo, Outlook, y Mail de Apple.</p><p>React Email también provee múltiples <a href="https://react.email/components">componentes UI</a> que te permiten personalizar las plantillas de email según tu diseño preferido usando componentes de React JSX/TSX.</p><p>Instala el paquete React Email y sus componentes al ejecutar el fragmento de código de abajo:</p><pre><code class="language-bash">npm install react-email -D -E
npm install @react-email/components -E
</code></pre><p>Incluye este escript en tu archivo <code>package.json</code>. Dirige a React Email a dónde están localizados las plantillas de email en tu proyecto.</p><pre><code class="language-json">  "scripts": {
    "email": "email dev --dir src/emails"
  },
</code></pre><p>Una de las características de React Email es la habilidad de pre-visualizar tu plantilla de email en tu navegador durante el desarrollo, permitiéndote ver cómo aparecerá en el email del receptor.</p><p>Así que luego, crea una carpeta <code>emails</code> que contenga un archivo <code>TicketCreated.tsx</code> dentro de la carpeta <code>src</code> de Next.js y copia el siguiente fragmento de código en el archivo:</p><pre><code class="language-typescript">import * as React from "react";
import {
    Body,
    Container,
    Head,
    Heading,
    Hr,
    Html,
    Link,
    Preview,
    Text,
    Tailwind,
} from "@react-email/components";

interface TicketCreatedProps {
    username: string;
    ticketID: string;
}

const baseUrl = process.env.VERCEL_URL || "http://localhost:3000";
</code></pre><p>En el fragmento de código de arriba, importamos los componentes necesarios para construir la plantilla de email.</p><p>Luego, agrega el componente <code>TicketCreated</code> al archivo para renderizar la plantilla de email usando los <a href="https://react.email/components">componentes de React Email</a>.</p><pre><code class="language-typescript">export const TicketCreated = ({ username, ticketID }: TicketCreatedProps) =&gt; {
    return (
        &lt;Html&gt;
            &lt;Head /&gt;
            &lt;Preview&gt;Email de Confirmación del Ticket de Soporte 🎉&lt;/Preview&gt;
            &lt;Tailwind&gt;
                &lt;Body className='bg-white my-auto mx-auto font-sans px-2'&gt;
                    &lt;Container className='border border-solid border-[#eaeaea] rounded my-[40px] mx-auto p-[20px] max-w-[465px]'&gt;
                        &lt;Heading className='text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0'&gt;
                            Tu Ticket ha sido creado
                        &lt;/Heading&gt;
                        &lt;Text className='text-black text-[14px] leading-[24px]'&gt;
                        Hola {username},
                        &lt;/Text&gt;
                        &lt;Text className='text-black text-[14px] leading-[24px]'&gt;
                            &lt;strong&gt;El Ticket de Soporte&lt;/strong&gt; (&lt;Link href={`${baseUrl}/ticket/${ticketID}`} className='text-blue-600 no-underline'&gt;{`#${ticketID}`}&lt;/Link&gt;) ha sido creado con éxito.
                        &lt;/Text&gt;

                        &lt;Text className='text-black text-[14px] leading-[24px]'&gt;El Equipo de Soporte revisará tu ticket y volverán contigo en breve.
                        &lt;/Text&gt;

                        &lt;Hr className='border border-solid border-[#eaeaea] my-[26px] mx-0 w-full' /&gt;
                        &lt;Text className='text-[#666666] text-[12px] leading-[24px]'&gt;Este mensaje estaba dirigido a {" "} &lt;span className='text-black'&gt;{username}&lt;/span&gt;. Si no creaste este ticket, por favor ignora este email.
                        &lt;/Text&gt;
                    &lt;/Container&gt;
                &lt;/Body&gt;
            &lt;/Tailwind&gt;
        &lt;/Html&gt;
    );
};
</code></pre><p>Finalmente, expórtalo y agrega un valor predeterminado para las props:</p><pre><code class="language-typescript">TicketCreated.PreviewProps = {
    username: "alanturing",
    ticketID: "9083475",
} as TicketCreatedProps;

export default TicketCreated;
</code></pre><p>Ejecuta <code>npm run email</code> en tu terminal para pre-visualizar la plantilla de email.</p><figure class="kg-card kg-image-card"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733752824765/6c5e1518-fc85-4d79-bd05-f4a2c2381976.png" class="kg-image" alt="6c5e1518-fc85-4d79-bd05-f4a2c2381976" width="2553" height="1276" loading="lazy"></figure><p>Esta plantilla de email notifica a los clientes que su ticket de soporte ha sido creado y que alguien del equipo de soporte se comunicarán con ellos.</p><p>React Email ofrece una variedad de plantillas de email pre-diseñada, facilitando el crear email con estilos hermosos para distintos propósitos. Puedes revisar el <a href="https://demo.react.email/preview/notifications/vercel-invite-user">demo disponible para ver ejemplos</a> de lo que es posible.</p><h2 id="c-mo-enviar-emails-con-resend"><strong>Cómo enviar Emails con Resend</strong></h2><p>Resend es una API de email sencilla que te permite enviar emails dentro de tu aplicación de software. Soporta un montón de lenguajes de programación, incluyendo JavaScript (Next.js, Express, Node.js), Python, PHP, Go, and Rust, entre otros.</p><p>Resend y React Email pueden ser fácilmente integrados juntamente ya que el co-fundador de Resend, <a href="https://github.com/bukinoshita">Bu Kinoshita</a>, es también el creador de React Email.</p><p>Crea una cuenta en <a href="https://resend.com/docs/send-with-nextjs">Resend</a>. Una vez que iniciaste sesión, navega hacia la sección de claves de API en tu panel y copia tu clave de API en un archivo <code>.env.local</code>.</p><figure class="kg-card kg-image-card"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733753483127/4a289abc-b7e5-4c81-b7ee-e8f084354fae.png" class="kg-image" alt="4a289abc-b7e5-4c81-b7ee-e8f084354fae" width="2560" height="1384" loading="lazy"></figure><pre><code class="language-bash">//👇🏻 archivo .env.local 
RESEND_API_KEY=&lt;RESEND_API_KEY&gt;
</code></pre><p>Actualiza el endpoint de la API para enviar un email usando la plantilla de React Email, como se muestra abajo:</p><pre><code class="language-typescript">import { NextRequest, NextResponse } from "next/server";
//👇🏻 función generado del ID del boleto
import { v4 as generateID } from "uuid";
//👇🏻 importa la plantilla de email
import TicketCreated from "@/emails/TicketCreated";
//👇🏻 imports Resend
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY);

export async function POST(req: NextRequest) {
    //👇🏻 acepta la entrada del cliente desde el frontend
    const { name, email, subject, content } = await req.json();
    //👇🏻 los registra
    console.log({ name, email, subject, content });
    //👇🏻 envia una email usando la plantilla de email
    const { data, error } = await resend.emails.send({
        from: "Acme &lt;onboarding@resend.dev&gt;",
        to: [email],
        subject: "Email de Confirmación del Ticket 🎉",
        react: TicketCreated({ username: name, ticketID: generateID() }),
    });

    if (error) {
        return NextResponse.json(
            { message: "Hubo un error al enviar el email" },
            { status: 500 }
        );
    }

    return NextResponse.json({
        message: "El email se envió con éxito",
        data,
    });
}
</code></pre><p>¡Felicidades!🥳 Has completado este tutorial.</p><p>Aquí un breve demo de la aplicación:</p><figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.25%;" class="fluid-width-video-wrapper">
            <iframe width="560" height="315" src="https://www.youtube.com/embed/RsNAtwDjAEg" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy" style="box-sizing: inherit; margin: 0px; padding: 0px; border: 0px; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-optical-sizing: inherit; font-size-adjust: inherit; font-kerning: inherit; font-feature-settings: inherit; font-variation-settings: inherit; font-size: 27.5px; vertical-align: middle; aspect-ratio: 16 / 9; width: 720px; height: auto;" name="fitvid0"></iframe>
          </div>
        </div>
      </figure><h2 id="pr-ximos-pasos"><strong>Próximos Pasos</strong></h2><p>En este tutorial, aprendiste cómo crear plantillas de email con React Email y enviarlos usando Resend. Ambos paquetes te permiten integra la comunicación de email fácilmente con tus aplicaciones.</p><p>Ya sean simples las notificaciones de email, los boletines informativos, o campañas de marketing, React Email y Resend ofrecen una solución eficiente y personalizable para satisfacer tus necesidades.</p><p>Algunos recursos útiles incluyen:</p><ul><li><a href="https://react.email/components">Componentes pre-construidas de React Email</a></li><li><a href="https://react.email/docs/introduction">Documentación de React Email</a></li><li><a href="https://resend.com/docs/send-with-nextjs">Cómo instalar Resend en aplicaciones de Next.js</a></li></ul><p>¡Gracias por leer!</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Cómo construir una Aplicación de Chat en Tiempo Real con React, Node, Socket.io, y HarperDB ]]>
                </title>
                <description>
                    <![CDATA[ En este artículo, estaremos usando Socket.io y HarperDB para construir una aplicación chat fullstack de tiempo real con salas de chat. Este será un gran proyecto para aprender cómo armar las aplicaciones fullstack, y cómo crear una aplicación donde el back-end puede comunicarse con el front-end en tiempo real. Normalmente, ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/como-construir-una-aplicacion-de-chat-en-tiempo-real-con-react-node-socket-io-y-harperdb/</link>
                <guid isPermaLink="false">675af63196d716043ef3d8e3</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Socket.io ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HarperDB ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Elias Ezequiel Pereyra Gomez ]]>
                </dc:creator>
                <pubDate>Fri, 24 Jan 2025 16:28:21 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2024/12/pexels-keira-burton-6146929.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>En este artículo, estaremos usando Socket.io y HarperDB para construir una aplicación chat fullstack de tiempo real con salas de chat.</p><p>Este será un gran proyecto para aprender cómo armar las aplicaciones fullstack, y cómo crear una aplicación donde el back-end puede comunicarse con el front-end en tiempo real.</p><p>Normalmente, usando solicitudes HTTP, el servidor no puede enviar datos al cliente en tiempo real. Pero al usar Socket.io, el servidor es capaz de enviar información en tiempo real al cliente sobre algunos eventos que sucedieron en el servidor.</p><p>La aplicación que estaremos construyendo tiene dos páginas:</p><p>Una página <code>join-a-chat-room</code>:</p><figure class="kg-card kg-image-card"><img src="https://lh6.googleusercontent.com/SyHBvbkVavSJTxNV1nOi2-V_YYXm3upFOJvAzBXwd1VNu10SKV4WBSyQS1tdf4OhiDbqlq3sLqCxWRSJafZwhfcsp72DSKEy3-hk3JvNVGcmsSgkHHpEH69pnBDVKCv6bXiMza4cC4BZiLCOiqKPAIk" class="kg-image" alt="How our app home page will look: a form with username input, select room dropdown and Join Room button" width="600" height="400" loading="lazy"></figure><p>Y una página <code>chat-room</code>:</p><figure class="kg-card kg-image-card"><img src="https://lh6.googleusercontent.com/uRkjeHOuGGGf9HnK7bZ1Zd6WeNMo8kaR6Py0_RiEDx1VUuTPx4oYNvfmPlOxNLAicM7bnr9rm0oY0E7k0fwfaZIEz4K1V-5ejOM3ztmrmjIjC8OsRyzNf0HZurxMWUMzdLgic7o8oC-RQxELo8vdcVw" class="kg-image" alt="The finished chat page" width="600" height="400" loading="lazy"></figure><p>Aquí está lo que estaremos usando para construir este aplicación:</p><ul><li><strong><strong>Front</strong>-<strong>end</strong></strong>: <a href="https://reactjs.org/docs/create-a-new-react-app.html">React</a> (Un framework de front-end de JavaScript para construir aplicaciones interactvias)</li><li><strong><strong>Back</strong>-<strong>end</strong></strong>: <a href="https://nodejs.org/en/">Node</a> y <a href="https://expressjs.com/">Express</a> (Express es un framework muy popular de NodeJS que nos permite crear fácilmente APIs y back-ends)</li><li><strong>Base de Datos</strong>: <a href="https://harperdb.io/">HarperDB</a> (una plataforma de datos + aplicación que te permite solicitar datos usando SQL o NoSQL. HarperDB también tiene una API integrada, ahorrándonos el escribir mucho código de back-end)</li><li><strong>Comunicación en Tiempo Real</strong>: <a href="https://socket.io/docs/v3/">Socket.io</a> (¡ve abajo!)</li></ul><p><a href="https://github.com/DoableDanny/Realtime-chat-app-with-rooms">Aquí está el código fuente</a> (recuerda de darle una estrella ⭐).</p><h2 id="tabla-de-contenidos"><strong>Tabla de Contenidos</strong></h2><ol><li>¿<a href="#que-es-socketio">Qué es Socket.io?</a></li><li><a href="#configuracion-proyecto">Configuración del Proyecto</a></li><li><a href="#como-construir-pagina-join-a-room">Cómo construir la Página "Join a Room"</a></li><li><a href="#como-configurar-servidor">Cómo configurar el Servidor</a></li><li><a href="#como-crear-primer-escucha-evento-socketio">Cómo crear nuestro primer Escucha de Evento de Socket.io en el Servidor</a></li><li><a href="#como-funcionan-salas-socketio">Cómo funcionan las Salas en Socket.io</a></li><li><a href="#como-construir-pagina-chat">Cómo construir la Página Chat</a></li><li><a href="#como-crear-componente-messages">Cómo crear el componente Messages (B)</a></li><li><a href="#como-crear-esquema-y-tabla-harperdb">Cómo crear un Esquema y una Tabla en HarperDB</a></li><li><a href="#como-crear-componente-send-message">Cómo crear el Componente Send Message (C)</a></li><li><a href="#como-configurar-variables-entorno-harperdb">Cómo configurar las Variables de Entorno de HarperDB</a></li><li><a href="#como-permitir-a-usuarios-envien-mensajes-con-socketio">Cómo permitir a los Usuarios que se envíen Mensajes con Socket.io</a></li><li><a href="#como-obtener-mensajes-desde-harperdb">Cómo obtener los Mensajes desde HarperDB</a></li><li><a href="#como-mostrar-ultimos-100-mensajes-en-el-cliente">Cómo mostrar los Últimos 100 Mensajes en el Client</a>e</li><li><a href="#como-mostrar-sala-y-usuarios">Cómo mostrar la Sala y los Usuarios (A)</a></li><li><a href="#como-quitar-un-usuario-de-sala-socketio">Cómo quitar a un Usuario de una Sala de Socket.io</a></li><li><a href="#como-agregar-escucha-evento-disconnect">Cómo agregar el Escucha de Evento Disconnect de Socket.io</a></li></ol><!--kg-card-begin: html--><h2 id="que-es-socketio">¿Qué es Socket.io?</h2><!--kg-card-end: html--><p>Socket.io permite al servidor enviar información al cliente en tiempo real, cuando los eventos ocurren en el servidor.</p><p>Por ejemplo, si estuvieras jugando un juego de múltiple jugador, un evento podría ser tu "amigo" que consiguió un gol espectacular en contra tuya.</p><p>Con Socket.io, sabrías (casi) instantáneamente sobre conceder un gol.</p><p>Sin Socket.io, el cliente tendría que hacer múltiples llamadas <em>polling</em> de AJAX para verificar que el evento ha ocurrido en el servidor. Por ejemplo, el cliente podría usar JavaScript para verificar un evento en el servidor cada 5 segundos.</p><p>Socket.io significa que el cliente no tiene que hacer múltiples llamadas polling de AJAX para verificar si algunos eventos han ocurrido en el servidor. Más bien, el servidor envía la información al cliente tan pronto como lo obtenga. Mucho mejor.👌</p><p>Así que, Socket.io nos permite construir fácilmente aplicaciones de tiempo real, tales comos aplicaciones de chat y juegos multijugador.</p><!--kg-card-begin: html--><h2 id="configuracion-proyecto">Configuración del Proyecto</h2><!--kg-card-end: html--><h3 id="1-c-mo-configurar-nuestras-carpetas"><strong>1. Cómo configurar nuestras carpetas</strong></h3><p>Comienza un nuevo proyecto en tu editor de texto de prefrencia (VS Code para mí), y crea dos carpetas a nivel raíz llamados client y server.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/folder-structure.JPG" class="kg-image" alt="Realtime chat app folder structure" width="600" height="400" loading="lazy"></figure><p>Crearemos nuestra aplicación React Front-end &nbsp;en la carpeta client, y nuestro backend con Node/express en la carpeta server.</p><h3 id="2-c-mo-instalar-nuestras-dependencias-de-client"><strong>2. Cómo instalar nuestras dependencias de client</strong></h3><p>Abre una terminal en la raíz del proyecto (en VS Code, puedes hacer esto al presionar Ctrl + ' o al ir a <em>terminal-&gt;new terminal</em>).</p><p>Luego, instalaremos React en nuestra carpeta client:</p><pre><code class="language-bash">$ npx create-react-app client
</code></pre><p>Después de que React se haya instalado, ve hacia la carpeta client, e instala las siguientes dependencias:</p><pre><code class="language-bash">$ cd client
$ npm i react-router-dom socket.io-client
</code></pre><p>React-router-dom nos permitirá configurar las rutas a nuestros distintos componentes de React – esencialmente creando diferentes páginas.</p><p>Socket.io-client es la versión cliente de socket.io, que nos permite "emitir" eventos al servidor. Una vez que el servidor lo recibe, podemos usar la versión del servidor de socket.io para hacer cosas como enviar mensajes a los usuarios en la misma sala como el emisor, o unirse con un usuario a una sala de socket.</p><p>Ganarás un mejor entendimiento de esto más tarde cuando lleguemos a implementar estas ideas con código.</p><h3 id="3-c-mo-iniciar-la-aplicaci-n-de-react"><strong>3. Cómo iniciar la aplicación de React </strong></h3><p>Vamos a asegurarnos que todo está funcionando bien ejecutando el siguiente comando desde la carpeta client:</p><pre><code class="language-bash">$ npm start
</code></pre><p>Webpack construirá la aplicación de React y lo servirá en `http://localhost:3000`:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/react-is-running.JPG" class="kg-image" alt="Create react app up and running on localhost" width="600" height="400" loading="lazy"></figure><p>Ahora configuremos nuestra base de datos de HarperDB que usaremos para guardar mensajes de forma permanente enviados por los usuarios.</p><h3 id="c-mo-configurar-harperdb"><strong>Cómo configurar HarperDB</strong></h3><p>Primero, crea una <a href="https://studio.harperdb.io/">cuenta con HarperDB</a>.</p><p>Luego crea una nueva instancia en la nube de HarperDB.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/03/harper_instance.JPG" class="kg-image" alt="create HarperDB instance" width="600" height="400" loading="lazy"></figure><p>Para hacer las cosas fáciles, selecciona la instancia cloud:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/03/instance-type.JPG" class="kg-image" alt="select HarperDB instance type" width="600" height="400" loading="lazy"></figure><p>Selecciona el proveedor del cloud (Yo escojo AWS):</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/03/cloud_provider.JPG" class="kg-image" alt="select HarperDB cloud provider" width="600" height="400" loading="lazy"></figure><p>Brinda un nombre a tu instancia de la nube, y crea tus credenciales de la instancia:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/03/instance_credentials.JPG" class="kg-image" alt="select HarperDB instance credentials" width="600" height="400" loading="lazy"></figure><p>HarperDB tiene una capa gratuita generosa que podemos usar para este proyecto, así que selecciona ese:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/03/instance_specs.JPG" class="kg-image" alt="select HarperDB instance specs" width="600" height="400" loading="lazy"></figure><p>Verifica que tus detalles son correctos, luego crea la instancia.</p><p>Tomará unos pocos minutos para crear la instancia, así que continuemos y, ¡hagamos nuestro primer componente de React!</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/03/instance_loading.JPG" class="kg-image" alt="HarperDB instance loading" width="600" height="400" loading="lazy"></figure><!--kg-card-begin: html--><h2 id="como-construir-pagina-join-a-room">Cómo construir la Página "Join a Room"</h2><!--kg-card-end: html--><p>Nuestra página principal va a terminar luciendo así:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/home-page.JPG" class="kg-image" alt="How our app home page will look: a form with username input, select room dropdown and Join Room button" width="600" height="400" loading="lazy"></figure><p>El usuario ingresará un nombre de usuario, selecciona una sala de chat desde el despegable, luego haz clic en "Join Room". El usuario será dirigido a la página de la sala de chat.</p><p>Así que, hagamos esta página principal.</p><h3 id="1-c-mo-crear-el-formulario-html-y-agregar-estilos"><strong>1. Cómo crear el formulario HTML y agregar estilos</strong></h3><p>Crea un nuevo archivo en <code>src/pages/home/index.js</code>.</p><p>Agregaremos estilos básicos a nuestra aplicación usando módulos de CSS, así que crea un nuevo archivo: <code>src/pages/home/styles.module.css</code>.</p><p>Nuestra estructura de carpeta debería ahora lucir así:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/pages-folder-structure.JPG" class="kg-image" alt="pages folder with home page component" width="600" height="400" loading="lazy"></figure><p>Ahora creemos el formulario básico HTML:</p><pre><code class="language-jsx">// client/src/pages/home/index.js

import styles from './styles.module.css';

const Home = () =&gt; {
  return (
    &lt;div className={styles.container}&gt;
      &lt;div className={styles.formContainer}&gt;
        &lt;h1&gt;{`&lt;&gt;DevRooms&lt;/&gt;`}&lt;/h1&gt;
        &lt;input className={styles.input} placeholder='Username...' /&gt;

        &lt;select className={styles.input}&gt;
          &lt;option&gt;-- Select Room --&lt;/option&gt;
          &lt;option value='javascript'&gt;JavaScript&lt;/option&gt;
          &lt;option value='node'&gt;Node&lt;/option&gt;
          &lt;option value='express'&gt;Express&lt;/option&gt;
          &lt;option value='react'&gt;React&lt;/option&gt;
        &lt;/select&gt;

        &lt;button className='btn btn-secondary'&gt;Join Room&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default Home;
</code></pre><p>Arriba, tenemos un input de texto sencillo para capturar el nombre de usuario, y un despegable seleccionable con algunas opciones por defecto para que el usuario seleccione una sala de chat para unirse.</p><p>Ahora importemos este componente en <code>App.js</code> y configuremos una ruta para el componente usando el paquete <code>react-router-dom</code>. Este será nuestra página principal, así que la ruta será <code>/</code>:</p><pre><code class="language-jsx">// client/src/App.js

import './App.css';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './pages/home';

function App() {
  return (
    &lt;Router&gt;
      &lt;div className='App'&gt;
        &lt;Routes&gt;
          &lt;Route path='/' element={&lt;Home /&gt;} /&gt;
        &lt;/Routes&gt;
      &lt;/div&gt;
    &lt;/Router&gt;
  );
}

export default App;
</code></pre><p>Ahora agreguemos algunos estilos básicos para hacer que nuestra aplicación luzca mas presentable:</p><pre><code class="language-css">/* client/src/App.css */

html * {
  font-family: Arial;
  box-sizing: border-box;
}
body {
  margin: 0;
  padding: 0;
  overflow: hidden;
  background: rgb(63, 73, 204);
}
::-webkit-scrollbar {
  width: 20px;
}
::-webkit-scrollbar-track {
  background-color: transparent;
}
::-webkit-scrollbar-thumb {
  background-color: #d6dee1;
  border-radius: 20px;
  border: 6px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover {
  background-color: #a8bbbf;
}
.btn {
  padding: 14px 14px;
  border-radius: 6px;
  font-weight: bold;
  font-size: 1.1rem;
  cursor: pointer;
  border: none;
}
.btn-outline {
  color: rgb(153, 217, 234);
  border: 1px solid rgb(153, 217, 234);
  background: rgb(63, 73, 204);
}
.btn-primary {
  background: rgb(153, 217, 234);
  color: rgb(0, 24, 111);
}
.btn-secondary {
  background: rgb(0, 24, 111);
  color: #fff;
}
</code></pre><p>También agreguemos los estilos específicos a nuestro componente de la página principal:</p><pre><code class="language-css">/* client/src/pages/home/styles.module.css */

.container {
  height: 100vh;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgb(63, 73, 204);
}
.formContainer {
  width: 400px;
  margin: 0 auto 0 auto;
  padding: 32px;
  background: lightblue;
  border-radius: 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
}
.input {
  width: 100%;
  padding: 12px;
  border-radius: 6px;
  border: 1px solid rgb(63, 73, 204);
  font-size: 0.9rem;
}
.input option {
  margin-top: 20px;
}
</code></pre><p>También hagamos que el botón "Join Room" tenga el ancho completo agregando un atributo de estilo:</p><pre><code class="language-jsx">// client/src/pages/home/index.js

&lt;button className='btn btn-secondary' style={{ width: '100%' }}&gt;Join Room&lt;/button&gt;
</code></pre><p>Nuestra página principal ahora se ve sólido:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/home-page-html.JPG" class="kg-image" alt="Fully-styled home page" width="600" height="400" loading="lazy"></figure><h3 id="2-c-mo-agregar-funcionalidad-al-formulario-join-room"><strong>2. Cómo agregar funcionalidad al formulario Join Room</strong></h3><p>Ahora tenemos un formulario básico y estilos, así que es tiempo de agregar algo de funcionalidad.</p><p>Esto es lo que queremos que suceda cuando el usuario haga clic en el botón "Join Room":</p><ol><li>Verificar que el nombre de usuario y los campos de la sala sean rellenados.</li><li>Si es así, emitimos un evento de socket a nuestro servidor.</li><li>Redireccionar al usuario a la página Chat (el cual crearemos luego).</li></ol><p>Vamos a necesitar crear algo de estado para almacenar los valores <em>nombre de usuario</em> y <em>room</em>. También necesitamos crear una instancia del socket.</p><p>Podríamos crear estos estados directamente dentro de nuestro componente <em>home</em>, pero nuestra página <em>Chat</em> también necesitará acceder al <em>username, <em>room</em> y <em>socket</em></em>. Así que elevaremos el estado al <code>App.js</code>, donde podemos pasar estas variables a las páginas <em>HomePage</em> y <em>Chat</em>.</p><p>Así que, creemos nuestro estado y configuremos un socket en <code>App.js</code>, y pasemos estas variables como props al componente. También pasaremos la función <code>setState</code> de forma que podamos alterar el estado:</p><pre><code class="language-jsx">// client/src/App.js

import './App.css';
import { useState } from 'react'; // Add this
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import io from 'socket.io-client'; // Add this
import Home from './pages/home';

const socket = io.connect('http://localhost:4000'); // Add this -- our server will run on port 4000, so we connect to it from here

function App() {
  const [username, setUsername] = useState(''); // Add this
  const [room, setRoom] = useState(''); // Add this

  return (
    &lt;Router&gt;
      &lt;div className='App'&gt;
        &lt;Routes&gt;
          &lt;Route
            path='/'
            element={
              &lt;Home
                username={username} // Add this
                setUsername={setUsername} // Add this
                room={room} // Add this
                setRoom={setRoom} // Add this
                socket={socket} // Add this
              /&gt;
            }
          /&gt;
        &lt;/Routes&gt;
      &lt;/div&gt;
    &lt;/Router&gt;
  );
}

export default App;
</code></pre><p>Ahora podemos acceder a estos props en nuestro componente <em>Home</em>. Usaremos la <a href="https://www.freecodecamp.org/espanol/news/como-usar-la-desestructuracion-de-arreglos-y-objetos-en-javascript/">destructuración</a> para obtener estas props:</p><pre><code class="language-jsx">// client/src/pages/home/index.js

import styles from './style.module.css';

const Home = ({ username, setUsername, room, setRoom, socket }) =&gt; {
  return (
    // ...
  );
};

export default Home;
</code></pre><p>Cuando el usuario ingrese su nombre de usuario o seleccione una sala, necesitamos actualizar las variables de estado <code>username</code> y <code>room</code><em>:</em></p><pre><code class="language-jsx">// client/src/pages/home/index.js

// ...

const Home = ({ username, setUsername, room, setRoom, socket }) =&gt; {
  return (
    &lt;div className={styles.container}&gt;
      // ...
        &lt;input
          className={styles.input}
          placeholder='Username...'
          onChange={(e) =&gt; setUsername(e.target.value)} // Add this
        /&gt;

        &lt;select
          className={styles.input}
          onChange={(e) =&gt; setRoom(e.target.value)} // Add this
        &gt;
         // ...
        &lt;/select&gt;

        // ...
    &lt;/div&gt;
  );
};

export default Home;
</code></pre><p>Ahora estamos capturamos los datos ingresados por el usuario, podemos crear una función <a href="https://www.freecodecamp.org/espanol/news/funciones-callback-en-javascript-que-son-los-callback-en-js-y-como-usarlos/">callback</a> <em>joinRoom()</em> para cuando el usuario haga clic en el botón "Join Room":</p><pre><code class="language-jsx">// client/src/pages/home/index.js

// ...

const Home = ({ username, setUsername, room, setRoom, socket }) =&gt; {

  // Add this
  const joinRoom = () =&gt; {
    if (room !== '' &amp;&amp; username !== '') {
      socket.emit('join_room', { username, room });
    }
  };

  return (
    &lt;div className={styles.container}&gt;
      // ...

        &lt;button
          className='btn btn-secondary'
          style={{ width: '100%' }}
          onClick={joinRoom} // Add this
        &gt;
          Join Room
        &lt;/button&gt;
      // ...
    &lt;/div&gt;
  );
};

export default Home;
</code></pre><p>Arriba, cuando el usuario haga clic en el botón, un evento del socket llamado <code>joinroom</code> se emite, juntamente con un objeto conteniendo el nombre de usuario y la sala seleccionada. Este evento será recibido por nuestro servidor un poco más tarde cuando haremos algo de magia.</p><p>Para terminar nuestra página principal, necesitamos agregar una redirección al final de nuestra función <code>joinRoom()</code> para llevar al usuario a la página <em>/chat</em>:</p><pre><code class="language-jsx">// client/src/pages/home/index.js

// ...
import { useNavigate } from 'react-router-dom'; // Agrega esto

const Home = ({ username, setUsername, room, setRoom, socket }) =&gt; {
  const navigate = useNavigate(); // Agrega esto

  const joinRoom = () =&gt; {
    if (room !== '' &amp;&amp; username !== '') {
      socket.emit('join_room', { username, room });
    }

    // Redirecciona a /chat
    navigate('/chat', { replace: true }); // Agrega esto
  };

 // ...
</code></pre><p>Pruébalo: escribe un nombre de usuario y selecciona una sala, luego haz clic en <em>Join Room</em>. Deberías ser dirigido a la ruta <code>http://localhost:3000/chat</code> – actualmente una página vacía.</p><p>Pero antes que creemos nuestra Página <em>Chat</em>, tengamos algo ejecutándose en el servidor.</p><!--kg-card-begin: html--><h2 id="como-configurar-servidor">Cómo configurar el Servidor</h2><!--kg-card-end: html--><p>En el servidor, vamos a escuchar los eventos del socket que son emitidos desde el frontend. Actualmente, solamente tenemos un evento <code>join_room</code> siendo emitido desde React, así que agregaremos este escucha de evento primero.</p><p>Pero antes de eso, necesitamos instalar nuestras dependencias del servidor y tener el servidor activo y ejecutándose.</p><h3 id="1-c-mo-instalar-las-dependencias-del-servidor"><strong>1. Cómo instalar las dependencias del servidor</strong></h3><p>Abre una nueva terminal (en VS Code: <code>Terminal-&gt;Nueva Terminal</code>), cambia la carpeta a nuestra carpeta de servidor, inicializa un archivo package.json, e instala las siguientes dependencias:</p><pre><code class="language-bash">$ cd server
$ npm init -y
$ npm i axios cors express socket.io dotenv
</code></pre><ul><li><strong>Axios</strong> es un paquete usado comunmente para hacer solicitudes fácilmente a APIs.</li><li><strong>Cors</strong> permite a nuestro cliente hacer solicitudes a otros orígenes – necesario para socket.io para que funcione de forma apropiada. Ve ¿<a href="https://aws.amazon.com/es/what-is/cross-origin-resource-sharing/">Qué es el CORS?</a> si no has escuchado de CORS antes.</li><li><strong>Express</strong> es un framework de Node.js que nos permite escribir nuestro backend más fácilmente con menos código.</li><li><strong>Socket.io</strong> es una librería que permite que el cliente y el servidor se comunique a tiempo real – el cual no es posible con solicitudes estándares de HTTP.</li><li><strong>Dotenv</strong> es un módulo que nos permite almacenar claves privadas y contraseñas de forma segura, y cargarlos en nuestro códio cuando sea necesario.</li></ul><p>También instalaremos <strong>nodemon</strong> como una dependencia dev, así no tenemos que reiniciar nuestro servidor cada vez que hagamos un cambio al código – ahorrándonos tiempo y energía:</p><pre><code class="language-bash">$ npm i -D nodemon
</code></pre><h3 id="2-c-mo-iniciar-nuestro-servidor"><strong>2. Cómo iniciar nuestro servidor</strong></h3><p>Crea un archivo <code>index.js</code> en la raíz de nuestra carpeta server, y agrega el siguiente código para iniciar el servidor:</p><pre><code class="language-javascript">// server/index.js

const express = require('express');
const app = express();
const http = require('http');
const cors = require('cors');

app.use(cors()); // Add cors middleware

const server = http.createServer(app);

server.listen(4000, () =&gt; 'Server is running on port 4000');
</code></pre><p>Abre el archivo <code>package.json</code> en nuestro servidor, y agrega un script que nos permitirá usar nodemon en desarrollo:</p><pre><code class="language-json">{
  ...
  "scripts": {
    "dev": "nodemon index.js"
  },
  ...
}
</code></pre><p>Ahora, iniciemos nuestro servidor al ejecutar el siguiente comando:</p><pre><code class="language-bash">$ npm run dev
</code></pre><p>Podemos verificar rápidamente que nuestro servidor se está ejecutando correctamente al agregar un manejador de solicitudes get:</p><pre><code class="language-javascript">// server/index.js

const express = require('express');
const app = express();
http = require('http');
const cors = require('cors');

app.use(cors()); // Agrega cors middleware

const server = http.createServer(app);

// Agrega esto
app.get('/', (req, res) =&gt; {
  res.send('Hello world');
});

server.listen(4000, () =&gt; 'El servidor está ejecutándose en el puerto 3000');
</code></pre><p>Ahora ve a <a href="http://localhost:4000/"><code>http://localhost:4000/</code></a>:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/localhost4000.JPG" class="kg-image" alt="Image" width="600" height="400" loading="lazy"></figure><p>Nuestro servidor está activo y en ejecución. ¡Ahora es tiempo de hacer cosas de Socket.io en el servidor!</p><!--kg-card-begin: html--><h2 id="como-crear-primer-escucha-evento-socketio">Cómo crear nuestro primer Escucha de Evento de Socket.io en el Servidor</h2><!--kg-card-end: html--><p>Recuerda que cuando emitimos un evento <code>joinroom</code> desde el cliente? Bueno, pronto vamos a estar escuchar ese evento en el servidor y agregar el usuario a una sala del socket.</p><p>Pero primero, necesitamos escuchar cuando un cliente se conecte al servidor a través de <code>socket.io-client</code>.</p><pre><code class="language-javascript">// server/index.js

const express = require('express');
const app = express();
http = require('http');
const cors = require('cors');
const { Server } = require('socket.io'); // Agrega esto

app.use(cors()); // Agrega cors middleware

const server = http.createServer(app); // Agrega esto

// Agrega esto
// Crea un servidor io y permite CORS desde http://localhost:3000 con metodos GET y POST
const io = new Server(server, {
  cors: {
    origin: 'http://localhost:3000',
    methods: ['GET', 'POST'],
  },
});

// Agrega esto
// Escucha cuando el cliente se conecta a traves de socket.io-client
io.on('connection', (socket) =&gt; {
  console.log(`User connected ${socket.id}`);

  // Podemos escribir nuestro our socket event listeners in here...
});

server.listen(4000, () =&gt; 'El servidor está ejecutándose en el puerto 3000');
</code></pre><p>Ahora, cuando el cliente se conecta desde el frontend, el backend captura el evento de la conexión, y registrará <code>User connected</code> con el id único del socket para ese cliente particular.</p><p>Probemos si el servidor ahora está captrando el evento de la conección desde el cliente. Ve a tu aplicación de React en <code>http://localhost:3000/</code> y refresca la página.</p><p>Deberías ver el siguiente registro en tu consola de terminal del servidor:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/user-connected.JPG" class="kg-image" alt="Image" width="600" height="400" loading="lazy"></figure><p>Genial, nuestro cliente se ha conectado a nuestro servidor a través de socket.io. Nuestro cliente y servidor ahora se pueden comunicar en, ¡tiempo real!</p><!--kg-card-begin: html--><h2 id="como-funcionan-salas-socketio">Cómo funcionan las Salas en Socket.io</h2><!--kg-card-end: html--><p>Desde la documentación de <a href="https://socket.io/docs/v3/rooms/">Socket.io</a> (no está disponible en español todavía):</p><blockquote><em><em>"</em>Una<em> </em>sala<em> </em>e<em>s </em>u<em>n</em> canal<em> </em>arbitrario<em> </em>al que<em> sockets </em>se puede unir (<em><code>join</code></em>)<em> </em>y abandonar (<em><code>leave</code></em>)<em>. </em>Puede ser usado<em> </em>para emitir<em> event</em>o<em>s </em>a<em> </em>un<em> </em>subconjunto<em> </em>de<em> client</em>e<em>s."</em></em></blockquote><p>Así que, podemos unir el usuario a una sala, y luego el servidor puede enviar mensajes a todos los usuarios en esa sala – permitiendo a los usuarios enviarse mensajes en tiempo real. ¡Genial! </p><h3 id="c-mo-unir-el-usuario-a-una-sala-de-socket-io"><strong>Cómo unir el usuario a una sala de Socket.io</strong></h3><p>Una vez que el usuario se ha conectado a través de Socket.io, podemos agregar nuestros escuchas de eventos del socket en el servidor para escuchar eventos emitidos desde el cliente. También, podemos emitir eventos en el servidor, y esucharlos en el cliente.</p><p>Ahora escuchemos por el evento <code>joinroom</code>, que capture los datos (nombre de usuario y sala), y agregue al usuario a una sala de socket:</p><pre><code class="language-javascript">// server/index.js

// Escuchar cuando el cliente se conecta a través de socket.io-client
io.on('connection', (socket) =&gt; {
  console.log(`User connected ${socket.id}`);

  // Agrega esto
  // Agrega un usuario a una sala
  socket.on('join_room', (data) =&gt; {
    const { username, room } = data; // Datos enviados desde el cliente cuando el evento join_room se emite
    socket.join(room); // Une el usuario a una sala de socket
  });
});
</code></pre><h3 id="c-mo-enviar-un-mensaje-a-los-usuarios-en-una-sala"><strong>Cómo enviar un mensaje a los usuarios en una sala</strong></h3><p>Ahora enviemos un mensaje a todos los usuarios en la sala, aparte del usuario que se acaba de unir, para notificarlos que un nuevo usuario se ha unido:</p><pre><code class="language-javascript">// server/index.js

const CHAT_BOT = 'ChatBot'; // Agrega esto
// Escucha cuando el cliente se conecta a través de socket.io-client
io.on('connection', (socket) =&gt; {
  console.log(`User connected ${socket.id}`);

  // Agrega un usuario a una sala
  socket.on('join_room', (data) =&gt; {
    const { username, room } = data; // Datos enviados desde el cliente cuando el evento join_room se emite
    socket.join(room); // Une el usuario a una sala de socket

    // Agrega esto
    let __createdtime__ = Date.now(); // Marcas de tiempo actuales
    // Envía un mensaje a todos los usuario en la sala, aparte del usuario que se acaba de unir
    socket.to(room).emit('receive_message', {
      message: `${username} has joined the chat room`,
      username: CHAT_BOT,
      __createdtime__,
    });
  });
});
</code></pre><p>Arriba, estamos emitiendo un evento <code>receive_message</code> a todos los clientes en la sala en el que el usuario se acaba de unir, juntamente con algo de dato: el mensaje, el nombre de usuario quien envió el mensaje, y el tiempo en que el mensaje fue enviado.</p><p>Agregaremos un escucha de evento en nuestra aplicación de React un poco después para capturar este evento, y muestra el mensaje en la pantalla.</p><p>También enviemos un mensaje de bienvenida al nuevo usuario recién unido:</p><pre><code class="language-javascript">// server/index.js

io.on('connection', (socket) =&gt; {
  // ...

    // Agrega esto
    // Envía el msg de bienvenida al usuario que se acaba de unir
    socket.emit('receive_message', {
      message: `Welcome ${username}`,
      username: CHAT_BOT,
      __createdtime__,
    });
  });
});
</code></pre><p>Cuando agregamos un usuario a una sala de Socket.io, Socket.io solamente almacena los ids de los sockets para cada usuario. Pero necesitaremos los nombres de usuario de todos en la sala, así también como el nombre de sala. Así que, almacenemos esos datos en variables en el servidor:</p><pre><code class="language-javascript">// server/index.js

// ...

const CHAT_BOT = 'ChatBot';
// Agrega esto
let chatRoom = ''; // Ej. javascript, node,...
let allUsers = []; // Todos los usuarios en la sala de chat actual

// Escucha cuando el cliente se conecta a través de socket.io-client
io.on('connection', (socket) =&gt; {
    // ...

    // Agrega esto
    // Guarda el nuevo usuario a la sala
    chatRoom = room;
    allUsers.push({ id: socket.id, username, room });
    chatRoomUsers = allUsers.filter((user) =&gt; user.room === room);
    socket.to(room).emit('chatroom_users', chatRoomUsers);
    socket.emit('chatroom_users', chatRoomUsers);
  });
});
</code></pre><p>Arriba, también estamos enviando un arreglo de todo el chatRoomUsers de nuevo al cliente a través del evento <code>chatroomusers</code>, así que podemos listar todos los nombres de usuario en el frontend.</p><p>Antes que agreguemos cualquier otro código a nuestro servidor, volvamos a nuestro frontend y creamos la página <em>Chat</em> – así podemos probar si estamos recibiendo los eventos <code>receivemessage</code>.</p><!--kg-card-begin: html--><h2 id="como-construir-pagina-chat">Cómo construir la Página Chat</h2><!--kg-card-end: html--><p>En tu carpeta cliente, crea dos nuevos archivos:</p><ol><li><code>src/pages/chat/index.js</code></li><li><code>src/pages/chat/styles.module.css</code></li></ol><p>Agreguemos algunos estilos que usaremos en nuestra página <em>Chat</em> y componentes:</p><pre><code class="language-css">/* client/src/pages/chat/styles.module.css */

.chatContainer {
  max-width: 1100px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr 4fr;
  gap: 20px;
}

/* Room and users component */
.roomAndUsersColumn {
  border-right: 1px solid #dfdfdf;
}
.roomTitle {
  margin-bottom: 60px;
  text-transform: uppercase;
  font-size: 2rem;
  color: #fff;
}
.usersTitle {
  font-size: 1.2rem;
  color: #fff;
}
.usersList {
  list-style-type: none;
  padding-left: 0;
  margin-bottom: 60px;
  color: rgb(153, 217, 234);
}
.usersList li {
  margin-bottom: 12px;
}

/* Messages */
.messagesColumn {
  height: 85vh;
  overflow: auto;
  padding: 10px 10px 10px 40px;
}
.message {
  background: rgb(0, 24, 111);
  border-radius: 6px;
  margin-bottom: 24px;
  max-width: 600px;
  padding: 12px;
}
.msgMeta {
  color: rgb(153, 217, 234);
  font-size: 0.75rem;
}
.msgText {
  color: #fff;
}

/* Message input and button */
.sendMessageContainer {
  padding: 16px 20px 20px 16px;
}
.messageInput {
  padding: 14px;
  margin-right: 16px;
  width: 60%;
  border-radius: 6px;
  border: 1px solid rgb(153, 217, 234);
  font-size: 0.9rem;
}
</code></pre><p>Ahora, veamos cómo terminará luciendo nuestra página <em>Chat</em>:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/chat-page.JPG" class="kg-image" alt="The finished chat page" width="600" height="400" loading="lazy"></figure><p>Agregando todo el código y lógica para esta página en un archivo podría volverse confuso y difícil de manejar, así que aprovechemos el hecho de que estamos usando un framework de frontend fabuloso (React) y <strong>dividir nuestra página en componentes:</strong></p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-248.png" class="kg-image" alt="The chat page split into three components" width="600" height="400" loading="lazy"></figure><h3 id="los-componentes-de-la-p-gina-chat-"><strong>Los componentes de la página chat:</strong></h3><p><strong><strong>A</strong></strong>: Contiene el nombre de la sala, una lista de usuarios en esa sala, y un botón "Leave" que quitar al usuario desde la sala.</p><p><strong><strong>B</strong></strong>: Los mensajes enviados. Al inicio de la renderización, los últimos 100 mensajes enviados en esa sala será solicitado desde la base de datos y mostrado al usuario.</p><p><strong><strong>C</strong></strong>: Una entrada y botón para escribir y enviar un mensaje.</p><p>Primero crearemos el componente B, así podemos mostrar los mensajes al usuario.</p><!--kg-card-begin: html--><h2 id="como-crear-componente-messages">Cómo crear el Componente Messages (B)</h2><!--kg-card-end: html--><p>Crea un nuevo archivo en <code>src/pages/chat/messages.js</code> y agrega el siguiente código:</p><pre><code class="language-jsx">// client/src/pages/chat/messages.js

import styles from './styles.module.css';
import { useState, useEffect } from 'react';

const Messages = ({ socket }) =&gt; {
  const [messagesRecieved, setMessagesReceived] = useState([]);

  // Se ejecuta cuando sea que un evento del socket es recibido desde el servidor
  useEffect(() =&gt; {
    socket.on('receive_message', (data) =&gt; {
      console.log(data);
      setMessagesReceived((state) =&gt; [
        ...state,
        {
          message: data.message,
          username: data.username,
          __createdtime__: data.__createdtime__,
        },
      ]);
    });

    // Quita la escucha del evento al desmontar el componente
    return () =&gt; socket.off('receive_message');
  }, [socket]);

  // dd/mm/yyyy, hh:mm:ss
  function formatDateFromTimestamp(timestamp) {
    const date = new Date(timestamp);
    return date.toLocaleString();
  }

  return (
    &lt;div className={styles.messagesColumn}&gt;
      {messagesRecieved.map((msg, i) =&gt; (
        &lt;div className={styles.message} key={i}&gt;
          &lt;div style={{ display: 'flex', justifyContent: 'space-between' }}&gt;
            &lt;span className={styles.msgMeta}&gt;{msg.username}&lt;/span&gt;
            &lt;span className={styles.msgMeta}&gt;
              {formatDateFromTimestamp(msg.__createdtime__)}
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;p className={styles.msgText}&gt;{msg.message}&lt;/p&gt;
          &lt;br /&gt;
        &lt;/div&gt;
      ))}
    &lt;/div&gt;
  );
};

export default Messages;
</code></pre><p>Ahora, tenemos un hook <em>useEffect</em> que se ejecuta cuando sea que el evento del socket se recibe. Luego recibimos los datos del mensaje en el escucha del evento <code>receivemessage</code>. Desde ahí, ponemos el estado <code>messagesReceived</code>, el cual es un arreglo de objetos de mensaje conteniendo el mensaje, nombre de usuario del emisor, y la fecha del mensaje que fue enviado.</p><p>Importemos nuestro nuevo componentes de mensajes en la página <em>Chat</em>, y luego creemos una ruta para la página <em>Chat</em> en <code>App.js</code>:</p><pre><code class="language-jsx">// client/src/pages/chat/index.js

import styles from './styles.module.css';
import MessagesReceived from './messages';

const Chat = ({ socket }) =&gt; {
  return (
    &lt;div className={styles.chatContainer}&gt;
      &lt;div&gt;
        &lt;MessagesReceived socket={socket} /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default Chat;
</code></pre><pre><code class="language-jsx">// client/src/App.js

import './App.css';
import { useState } from 'react';
import Home from './pages/home';
import Chat from './pages/chat';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import io from 'socket.io-client';

const socket = io.connect('http://localhost:4000');

function App() {
  const [username, setUsername] = useState('');
  const [room, setRoom] = useState('');

  return (
    &lt;Router&gt;
      &lt;div className='App'&gt;
        &lt;Routes&gt;
          &lt;Route
            path='/'
            element={
              &lt;Home
                username={username}
                setUsername={setUsername}
                room={room}
                setRoom={setRoom}
                socket={socket}
              /&gt;
            }
          /&gt;
          {/* Add this */}
          &lt;Route
            path='/chat'
            element={&lt;Chat username={username} room={room} socket={socket} /&gt;}
          /&gt;
        &lt;/Routes&gt;
      &lt;/div&gt;
    &lt;/Router&gt;
  );
}

export default App;
</code></pre><p>Probemos esto, ve a la página principal y únete a una sala:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/joining-a-room.JPG" class="kg-image" alt="Joining a room as Dan" width="600" height="400" loading="lazy"></figure><p>Deberíamos ser llevados a la página <em>Chat</em>, y recibir un mensaje de bienvenida del <em>ChatBot</em>:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/welcome-message.JPG" class="kg-image" alt="Welcome message received from ChatBot" width="600" height="400" loading="lazy"></figure><p>Los usuarios ahora pueden ver los mensajes que reciben. ¡Genial!</p><p>Próximo: configurar nuestra base de datos así podemos guardar los mensajes permanentemente.</p><!--kg-card-begin: html--><h2 id="como-crear-esquema-y-tabla-harperdb">Cómo crear un Esquema y una Tabla en HarperDB</h2><!--kg-card-end: html--><p>Vuelve a tu dashboard de HarperDB, y haz clic en "browser". Luego crea un nuevo esquemos llamado "realtime_chat_app". Un esquema es simplemente un grupo de tablas.</p><p>Dentro de ese esquema, crea una tabla llamada "messages", con un atributo hash de "id".</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-258.png" class="kg-image" alt="Creating our schema and table in HarperDB" width="600" height="400" loading="lazy"></figure><p>Ahora tenemos necestiamos donde almacenar los mensajes, así que creemos el componente <em>SendMessage</em>.</p><!--kg-card-begin: html--><h2 id="como-crear-componente-send-message">Cómo crear el componente Send Message (C)</h2><!--kg-card-end: html--><p>Crea el archivo <code>src/pages/chat/send-message.js</code> y agrega el siguiente código:</p><pre><code class="language-jsx">// client/src/pages/chat/send-message.js

import styles from './styles.module.css';
import React, { useState } from 'react';

const SendMessage = ({ socket, username, room }) =&gt; {
  const [message, setMessage] = useState('');

  const sendMessage = () =&gt; {
    if (message !== '') {
      const __createdtime__ = Date.now();
      // Envia un mensaje al servidor. No podemos especificar a quien enviamos el mensaje desde el frontend. Solamente podemos enviar al servidor. El servidor por lo tanto puede enviar un mensaje al resto de los usuarios en la sala
      socket.emit('send_message', { username, room, message, __createdtime__ });
      setMessage('');
    }
  };

  return (
    &lt;div className={styles.sendMessageContainer}&gt;
      &lt;input
        className={styles.messageInput}
        placeholder='Message...'
        onChange={(e) =&gt; setMessage(e.target.value)}
        value={message}
      /&gt;
      &lt;button className='btn btn-primary' onClick={sendMessage}&gt;
        Send Message
      &lt;/button&gt;
    &lt;/div&gt;
  );
};

export default SendMessage;
</code></pre><p>Arriba, cuando el usuario hace clic en el botón "Send Message", un evento del socket <code>send_message</code> se emite al servidor, junto con un objeto de mensaje. Manejaremos este evento en el servidor dentro de poco.</p><p>Importa <em>SendMessage</em> en nuestra página Chat:</p><pre><code class="language-js">// src/pages/chat/index.js

import styles from './styles.module.css';
import MessagesReceived from './messages';
import SendMessage from './send-message';

const Chat = ({ username, room, socket }) =&gt; {
  return (
    &lt;div className={styles.chatContainer}&gt;
      &lt;div&gt;
        &lt;MessagesReceived socket={socket} /&gt;
        &lt;SendMessage socket={socket} username={username} room={room} /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default Chat;
</code></pre><p>La página <em>Chat</em> ahora luce así:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-259.png" class="kg-image" alt="Chat page now has a message input where a message can be typed and sent" width="600" height="400" loading="lazy"></figure><p>Luego necesitamos configurar nuestras variables de entorno de HarperDB así podemos comenzar a interactuar con la base de datos.</p><!--kg-card-begin: html--><h2 id="como-configurar-variables-entorno-harperdb">Cómo configurar las Variables de Entorno de HarperDB </h2><!--kg-card-end: html--><p>Para que seas capaz de guardar los mensajes en HarperDB, necesitarás tu URL de instancia de HarperDB, y tu contraseña API.</p><p>En tu dashboard de HarperDB, haz clic en tu instancia, luego ve a "config". Encontrarás tu URL de instancia, y la cabecera de Autenticación de API de tu instancia – eso es, tu contraseña "super_user" que te permite realizar cualquier solicitud a la base de datos – ¡PARA TUS OJOS SOLAMENTE!</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-263.png" class="kg-image" alt="HarperDB instance URL and API auth header" width="600" height="400" loading="lazy"></figure><p>Guardaremos estas variables en un archivo .env. <strong>Advertencia: ¡No empujes el archivo .env a Github!</strong> Este archivo no debería ser visible públicamente. Las variables se cargan a través del servidor por detrás.</p><p>Crea los siguientes archivos y agrega tu URL y contraseña de HarperDB:</p><pre><code class="language-bash">// server/.env

HARPERDB_URL="&lt;tu url va aqui&gt;"
HARPERDB_PW="Basic &lt;tu contraseña aqui&gt;"
</code></pre><p>También crearemos un archivo <code>.gitignore</code> para evitar que el .env se suba a Github, juntamente con la carpeta <code>node_modules</code>:</p><pre><code class="language-bash">// server/.gitignore

.env
node_modules
</code></pre><p><strong>Nota</strong>: ser bueno en Git y Github es 100% imprescindible para todos los desarrolladores. Revisa <a href="https://www.doabledanny.com/git-workflows">mi artículo de los flujos de trabajo de Git</a> si necesitas mejorar tu juego de Git.</p><p>O si constantemente te encuentras revisando los mismos comandos de Git, y quieres una forma rápida de verlos, y copiar/pegar los comandos – revisa mi <a href="https://doabledanny.gumroad.com/l/git-commands-cheat-sheet-pdf">hoja de trucos de los comandos de Git en PDF</a> y mi <a href="https://doabledanny.gumroad.com/l/git-cheat-sheet-poster">póster de hoja de trucos de Git físico</a>.</p><p>Finalmente, carguemos nuestras variables de entorno en nuestro servidor al agregar este código en la parte superior de nuestro archivo <code>main</code>:</p><pre><code class="language-js">// server/index.js

require('dotenv').config();
console.log(process.env.HARPERDB_URL); // quita esto despues que hayas visto que funcionó
const express = require('express');
// ...
</code></pre><!--kg-card-begin: html--><h2 id="como-permitir-a-usuarios-envien-mensajes-con-socketio">Cómo permitir a los Usuarios que se envíen mensajes con Socket.io</h2><!--kg-card-end: html--><p>En el servidor, escucharemos el evento <code>sendmessage</code>, luego enviaremos el mensaje a todos los usuarios dentro de la sala:</p><pre><code class="language-js">// server/index.js

const express = require('express');
// ...
const harperSaveMessage = require('./services/harper-save-message'); // Agrega esto

// ...

// Escucha cuando el cliente se conecta a través de socket.io-client
io.on('connection', (socket) =&gt; {

  // ...

  // Agrega esto
  socket.on('send_message', (data) =&gt; {
    const { message, username, room, __createdtime__ } = data;
    io.in(room).emit('receive_message', data); // Envia a todos los usuarios en la sala, incluyendo al emisor
    harperSaveMessage(message, username, room, __createdtime__) // Guarda el mensaje en la bd
      .then((response) =&gt; console.log(response))
      .catch((err) =&gt; console.log(err));
  });
});

server.listen(4000, () =&gt; 'El Servidor se está ejecutando en el puerto 3000');
</code></pre><p>Ahora necesitamos crear la función <code>harperSaveMessage</code>. Crea un nuevo archivo en <code>server/services/harper-save-message.js</code>, y agrega lo siguiente:</p><pre><code class="language-js">// server/services/harper-save-message.js

var axios = require('axios');

function harperSaveMessage(message, username, room) {
  const dbUrl = process.env.HARPERDB_URL;
  const dbPw = process.env.HARPERDB_PW;
  if (!dbUrl || !dbPw) return null;

  var data = JSON.stringify({
    operation: 'insert',
    schema: 'realtime_chat_app',
    table: 'messages',
    records: [
      {
        message,
        username,
        room,
      },
    ],
  });

  var config = {
    method: 'post',
    url: dbUrl,
    headers: {
      'Content-Type': 'application/json',
      Authorization: dbPw,
    },
    data: data,
  };

  return new Promise((resolve, reject) =&gt; {
    axios(config)
      .then(function (response) {
        resolve(JSON.stringify(response.data));
      })
      .catch(function (error) {
        reject(error);
      });
  });
}

module.exports = harperSaveMessage;
</code></pre><p>Arriba, guardar los datos podria tomar un poco de tiempo, así que devolvemos una promesa el cual será resuelto si los datos se guardan satisfactoriamente, o rechazada si no se guardan.</p><p>Si te estás preguntando donde obtuve el código de arriba, HarperDB provee una sección increíble de "<a href="https://studio.harperdb.io/resources/examples/QuickStart%20Examples/Create%20dev%20Schema">ejemplos de código</a>" en su dashboard studio, el cual nos facilita la vida:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-265.png" class="kg-image" alt="HarperDB code examples" width="600" height="400" loading="lazy"></figure><p>¡Tiempo de descanso! Únete a una sala como un usuario, luego envía un mensaje. Luego ve a HarperDB y haz clic en "browse", luego haz clic en la tabla "messages". Deberías ver tú mensaje en la base de datos:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-264.png" class="kg-image" alt="Our first messages in the database" width="600" height="400" loading="lazy"></figure><p>Genial 😎. Así que, ¿ahora qué? Bueno, sería bueno si los últimos 100 mensajes enviados en la sala fueran cargados cuando un usuario se una a &nbsp;una sala, ¿no?</p><!--kg-card-begin: html--><h2 id="como-obtener-mensajes-desde-harperdb">Cómo obtener los mensajes desde HarperDB</h2><!--kg-card-end: html--><p>En el servidor, creemos una función que solicite los últimos 100 mensajes enviados en una sala particular (fíjate cómo HarperDB también nos permite usar solicitudes de SQL 👌):</p><pre><code class="language-js">// server/services/harper-get-messages.js

let axios = require('axios');

function harperGetMessages(room) {
  const dbUrl = process.env.HARPERDB_URL;
  const dbPw = process.env.HARPERDB_PW;
  if (!dbUrl || !dbPw) return null;

  let data = JSON.stringify({
    operation: 'sql',
    sql: `SELECT * FROM realtime_chat_app.messages WHERE room = '${room}' LIMIT 100`,
  });

  let config = {
    method: 'post',
    url: dbUrl,
    headers: {
      'Content-Type': 'application/json',
      Authorization: dbPw,
    },
    data: data,
  };

  return new Promise((resolve, reject) =&gt; {
    axios(config)
      .then(function (response) {
        resolve(JSON.stringify(response.data));
      })
      .catch(function (error) {
        reject(error);
      });
  });
}

module.exports = harperGetMessages;
</code></pre><p>Llamaremos a esta función cuando sea que un usuario se una a una sala:</p><pre><code class="language-js">// server/index.js

// ...
const harperSaveMessage = require('./services/harper-save-message');
const harperGetMessages = require('./services/harper-get-messages'); // Add this

// ...

// Escucha cuando el cliente se conecta a través de socket.io-client
io.on('connection', (socket) =&gt; {
  console.log(`User connected ${socket.id}`);

  // Agrega un usuario a una sala
  socket.on('join_room', (data) =&gt; {

    // ...

    // Agrega esto
    // Obtiene los últimos 100 mensajes enviados en la sala chat
    harperGetMessages(room)
      .then((last100Messages) =&gt; {
        // console.log('ultimos mensajes', last100Messages);
        socket.emit('last_100_messages', last100Messages);
      })
      .catch((err) =&gt; console.log(err));
  });

 // ...
</code></pre><p>Arriba, si los mensajes se solicitan de forma exitosa, emitimos un evento de Socket.io llamado <code>last_100_messages</code>. Ahora escucharemos este evento en el frontend.</p><!--kg-card-begin: html--><h2 id="como-mostrar-ultimos-100-mensajes-en-el-cliente">Cómo mostrar los últimos 100 mensajes en el Cliente</h2><!--kg-card-end: html--><p>Abajo, agregamos un hook useEffect que contiene un escucha de evento de Socket.io para el evento <code>last_100_messages</code><em>. </em>Desde ahí, los mensajes son ordenados en orden de fecha, con el más reciente al final, y el estado <code>messagesReceived</code> se actualiza.</p><p>Cuando <code>messagesReceived</code> se actualiza, un useEffect se ejecuta para desplazar el div <code>messageColumn</code> al mensaje más reciente. Esto mejora la experiencia de usuario de nuestra aplicación 👍.</p><pre><code class="language-js">// client/src/pages/chat/messages.js

import styles from './styles.module.css';
import { useState, useEffect, useRef } from 'react';

const Messages = ({ socket }) =&gt; {
  const [messagesRecieved, setMessagesReceived] = useState([]);

  const messagesColumnRef = useRef(null); // Add this

  // Se ejecuta cuando sea que un evento del socket se reciba desde el servidor
  useEffect(() =&gt; {
    socket.on('receive_message', (data) =&gt; {
      console.log(data);
      setMessagesReceived((state) =&gt; [
        ...state,
        {
          message: data.message,
          username: data.username,
          __createdtime__: data.__createdtime__,
        },
      ]);
    });

    // Quita el escucha de eventos al desmontarse el componente 
    return () =&gt; socket.off('receive_message');
  }, [socket]);

  // Agrega esto
  useEffect(() =&gt; {
    // Ultimos 100 mensajes enviados en la sala chat (solicitados desde la bd en el backend)
    socket.on('last_100_messages', (last100Messages) =&gt; {
      console.log('Last 100 messages:', JSON.parse(last100Messages));
      last100Messages = JSON.parse(last100Messages);
      // Ordena estos mensajes por __createdtime__
      last100Messages = sortMessagesByDate(last100Messages);
      setMessagesReceived((state) =&gt; [...last100Messages, ...state]);
    });

    return () =&gt; socket.off('last_100_messages');
  }, [socket]);

  // Agrega esto
  // Se desplaza al mensaje más reciente
  useEffect(() =&gt; {
    messagesColumnRef.current.scrollTop =
      messagesColumnRef.current.scrollHeight;
  }, [messagesRecieved]);

  // Agrega esto
  function sortMessagesByDate(messages) {
    return messages.sort(
      (a, b) =&gt; parseInt(a.__createdtime__) - parseInt(b.__createdtime__)
    );
  }

  // dd/mm/yyyy, hh:mm:ss
  function formatDateFromTimestamp(timestamp) {
    const date = new Date(timestamp);
    return date.toLocaleString();
  }

  return (
    // Agrega ref a este div
    &lt;div className={styles.messagesColumn} ref={messagesColumnRef}&gt;
      {messagesRecieved.map((msg, i) =&gt; (
        &lt;div className={styles.message} key={i}&gt;
          &lt;div style={{ display: 'flex', justifyContent: 'space-between' }}&gt;
            &lt;span className={styles.msgMeta}&gt;{msg.username}&lt;/span&gt;
            &lt;span className={styles.msgMeta}&gt;
              {formatDateFromTimestamp(msg.__createdtime__)}
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;p className={styles.msgText}&gt;{msg.message}&lt;/p&gt;
          &lt;br /&gt;
        &lt;/div&gt;
      ))}
    &lt;/div&gt;
  );
};

export default Messages;
</code></pre><!--kg-card-begin: html--><h2 id="como-mostrar-sala-y-usuarios">Cómo mostrar la Sala y los Usuarios (A)</h2><!--kg-card-end: html--><p>Hemos hecho los componentes B y C, así que terminemos todo al hacer A.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-248.png" class="kg-image" alt="The chat page split into three components" width="600" height="400" loading="lazy"></figure><p>En el servidor, cuando un usario se une a una sala, emitimos un evento <code>chatroomusers</code> que envía todos los usuarios de la sala a todos los clientes en esa sala. Escuchemos ese evento en un componente llamado <em>RoomAndUsers</em>.</p><p>Abajo, también hay un botón "Leave" que, cuando se presiona, hace la emisión de un evento <code>leaveroom</code> al servidor. Luego redirecciona el usuario devuelta a la página principal.</p><pre><code class="language-js">// client/src/pages/chat/room-and-users.js

import styles from './styles.module.css';
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

const RoomAndUsers = ({ socket, username, room }) =&gt; {
  const [roomUsers, setRoomUsers] = useState([]);

  const navigate = useNavigate();

  useEffect(() =&gt; {
    socket.on('chatroom_users', (data) =&gt; {
      console.log(data);
      setRoomUsers(data);
    });

    return () =&gt; socket.off('chatroom_users');
  }, [socket]);

  const leaveRoom = () =&gt; {
    const __createdtime__ = Date.now();
    socket.emit('leave_room', { username, room, __createdtime__ });
    // Redirecciona a la pagina principal
    navigate('/', { replace: true });
  };

  return (
    &lt;div className={styles.roomAndUsersColumn}&gt;
      &lt;h2 className={styles.roomTitle}&gt;{room}&lt;/h2&gt;

      &lt;div&gt;
        {roomUsers.length &gt; 0 &amp;&amp; &lt;h5 className={styles.usersTitle}&gt;Users:&lt;/h5&gt;}
        &lt;ul className={styles.usersList}&gt;
          {roomUsers.map((user) =&gt; (
            &lt;li
              style={{
                fontWeight: `${user.username === username ? 'bold' : 'normal'}`,
              }}
              key={user.id}
            &gt;
              {user.username}
            &lt;/li&gt;
          ))}
        &lt;/ul&gt;
      &lt;/div&gt;

      &lt;button className='btn btn-outline' onClick={leaveRoom}&gt;
        Leave
      &lt;/button&gt;
    &lt;/div&gt;
  );
};

export default RoomAndUsers;
</code></pre><p>Importemos este componente en la página <em>Chat</em>:</p><pre><code class="language-js">// client/src/pages/chat/index.js

import styles from './styles.module.css';
import RoomAndUsersColumn from './room-and-users'; // Add this
import SendMessage from './send-message';
import MessagesReceived from './messages';

const Chat = ({ username, room, socket }) =&gt; {
  return (
    &lt;div className={styles.chatContainer}&gt;
      {/* Agrega esto */}
      &lt;RoomAndUsersColumn socket={socket} username={username} room={room} /&gt;

      &lt;div&gt;
        &lt;MessagesReceived socket={socket} /&gt;
        &lt;SendMessage socket={socket} username={username} room={room} /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default Chat;
</code></pre><!--kg-card-begin: html--><h2 id="como-quitar-un-usuario-de-sala-socketio">Cómo quitar a un Usuarío de una sala de Socket.io</h2><!--kg-card-end: html--><p>Socket.io provee un método <code>leave()</code> que puedes usar para quitar a un usuario de una sala de Socket.io. También estamos manteniendo un registro de nuestros usuarios en un arreglo en la memoria del servidor, así que quitaremos al usuario de ese arreglo también:</p><pre><code class="language-js">// server/index.js

const leaveRoom = require('./utils/leave-room'); // Add this

// ...

// Escucha cuando el cliente se conecta a través de socket.io-client
io.on('connection', (socket) =&gt; {

  // ...

  // Agrega esto
  socket.on('leave_room', (data) =&gt; {
    const { username, room } = data;
    socket.leave(room);
    const __createdtime__ = Date.now();
    // Quita a un usuario de la memoria
    allUsers = leaveRoom(socket.id, allUsers);
    socket.to(room).emit('chatroom_users', allUsers);
    socket.to(room).emit('receive_message', {
      username: CHAT_BOT,
      message: `${username} has left the chat`,
      __createdtime__,
    });
    console.log(`${username} has left the chat`);
  });
});

server.listen(4000, () =&gt; 'El Servidor se está ejecutando en el puerto 3000');
</code></pre><p>Ahora necesitamos crear la función <code>leaveRoom()</code>:</p><pre><code class="language-js">// server/utils/leave-room.js

function leaveRoom(userID, chatRoomUsers) {
  return chatRoomUsers.filter((user) =&gt; user.id != userID);
}

module.exports = leaveRoom;
</code></pre><p>¿Por qué poner esta función corta en una carpeta <code>utils</code> aparte, te preguntas? Porque lo estaremos usando luego y no queremos repetirnos (mantenemos nuestro código <a href="https://es.wikipedia.org/wiki/No_te_repitas">DRY</a>). </p><p>Probémoslo, abre dos ventanas lado a lado, y únete a ambos chats:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-266.png" class="kg-image" alt="Two windows chatting in realtime." width="600" height="400" loading="lazy"></figure><p>Luego haz clic en el botón leave en la ventana 2:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-267.png" class="kg-image" alt="The user is removed from the chat when they click the Leave button" width="600" height="400" loading="lazy"></figure><p>El usuario se quita del chat, y un mensaje se envía a los otros usuarios – notificándolos que se ha ido. ¡Bien!</p><!--kg-card-begin: html--><h2 id="como-agregar-escucha-evento-disconnect">Cómo agregar el Escucha de Evento Disconnect de Socket.io</h2><!--kg-card-end: html--><p>¿Qué pasa si el usuario de alguna manera se desconecta del servidor, como si su internet se cayera? Socket.io provee un escucha de evento adjunto <code>disconnects</code> para esto. Agreguémosle a nuestro servidor para quitar a un usuario de la memoria cuando se desconecta:</p><pre><code class="language-js">// server/index.js

// ...

// Escucha cuando el cliente se conecta a través de socket.io-client
io.on('connection', (socket) =&gt; {

  // ...

  // Agrega esto
  socket.on('disconnect', () =&gt; {
    console.log('El usuario se desconectó del chat');
    const user = allUsers.find((user) =&gt; user.id == socket.id);
    if (user?.username) {
      allUsers = leaveRoom(socket.id, allUsers);
      socket.to(chatRoom).emit('chatroom_users', allUsers);
      socket.to(chatRoom).emit('receive_message', {
        message: `${user.username} se ha desconectado del chat.`,
      });
    }
  });
});

server.listen(4000, () =&gt; 'El Servidor se está ejecutando en el puerto 3000');
</code></pre><p>Y ahí lo tienes – haz construido una aplicación de chat de tiempo real fullstack con un frontend de React, un backend de Node/Express, y una base de datos de HarperDB. ¡Buen trabajo!</p><p>Para la próxima, planeo en revisar nuestras <a href="https://harperdb.io/docs/custom-functions/">Funciones Personalizadas de HarperDB</a>, el cual permite a los usuarios definir sus propios endpoints de API dentro de HarperDB. Esto significa que podemos construir nuestra aplicación completa, ¡en un sólo lugar! Mira un ejemplo de cómo HarperDB está colapsando el stack <a href="https://www.harperdb.io/post/mean-stack-alternative">en este artículo</a>.</p><h2 id="un-desaf-o-para-ti-"><strong>Un desafío para ti 💪</strong></h2><p>Si refrescas la página Chat, el nombre de usuario del usuario y la sala desaparecerán. Mira si puedes prevenir que esta información se pierda cuando el usuario refresque la página. Pista: ¡el <a href="https://www.w3schools.com/html/html5_webstorage.asp">local storage</a> podría ser útil!</p><h2 id="-gracias-por-leer-"><strong>¡Gracias<strong><strong> </strong></strong>por<strong><strong> </strong></strong>leer<strong><strong>!</strong></strong></strong></h2><p>Si te pareció útil este artículo, puedes:</p><ul><li><a href="https://www.youtube.com/channel/UC0URylW_U4i26wN231yRqvA">Suscribirte a mi canal de YouTube</a>. Estaré subiendo tutoriales en profunidad y vídeos de proyectos sobre React/NextJS/Node/Express.</li><li><a href="https://twitter.com/doabledanny">Sígueme en Twitter</a> donde comparto tweets sobre mi jornada de freelancing, proyectos propios, y aprendizajes actuales.</li><li><a href="https://doabledanny.gumroad.com/">Revisa mi almacén de Gumroad</a> donde hago hojas de trucos y pósteres útiles y populares (8000 descargas al tiempo de escribir).</li><li><a href="https://www.doabledanny.com/blog/">Revisa mi blog</a></li></ul> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Introducción a los Hooks Avanzados ]]>
                </title>
                <description>
                    <![CDATA[ Explorando useMemo, useCallback y useLayoutEffect en React React nos ha proporcionado herramientas poderosas para desarrollar aplicaciones web interactivas y eficientes. Con la introducción de los hooks, se abrió un mundo de posibilidades para manejar estados, efectos y funciones de manera más declarativa. Más allá de los hooks básicos (useState, useEffect), ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/introduccion-a-los-hooks-avanzados/</link>
                <guid isPermaLink="false">677b070dd2e79604442d277b</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Leonardo José Castillo Lacruz ]]>
                </dc:creator>
                <pubDate>Mon, 13 Jan 2025 14:49:22 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2025/01/portada.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="explorando-usememo-usecallback-y-uselayouteffect-en-react">Explorando useMemo, useCallback y useLayoutEffect en React</h2><p>React nos ha proporcionado herramientas poderosas para desarrollar aplicaciones web interactivas y eficientes. Con la introducción de los hooks, se abrió un mundo de posibilidades para manejar estados, efectos y funciones de manera más declarativa. Más allá de los hooks básicos (<code>useState</code>, <code>useEffect</code>), existen otros más avanzados como <code>useMemo</code>, <code>useCallback</code> y <code>useLayoutEffect</code>, diseñados para abordar desafíos específicos relacionados con el rendimiento y la experiencia del usuario.</p><p>En este artículo, exploraremos estos tres hooks en profundidad, desde sus fundamentos teóricos hasta su implementación práctica en una aplicación React que simula un ecommerce.</p><hr><h4 id="1-usememo-optimizando-c-lculos-innecesarios"><strong>1. useMemo: Optimizando cálculos innecesarios</strong></h4><p><strong>¿Qué es?</strong></p><p><code>useMemo</code> es un hook que nos permite memorizar el resultado de una función costosa para evitar que se recalculen innecesariamente en cada renderizado. Es especialmente útil cuando trabajamos con grandes conjuntos de datos o funciones intensivas en computación.</p><p><strong>¿Cómo funciona?</strong></p><p>Cuando React renderiza un componente, todas las funciones dentro del componente se ejecutan. Si una función realiza cálculos pesados, esto puede afectar significativamente el rendimiento. Aquí es donde entra <code>useMemo</code>, que guarda ("memoriza") el resultado de una función y solo la vuelve a ejecutar si alguna de las dependencias cambia.</p><p><strong>Características clave:</strong></p><ul><li>Memoriza valores computados.</li><li>Se usa principalmente para optimizar componentes que dependen de cálculos intensivos.</li><li>Acepta dos argumentos: una función y un arreglo de dependencias.</li></ul><p><strong>Ejemplo básico:</strong></p><pre><code>const memoizedValue = useMemo(() =&gt; { 
	return computeExpensiveValue(a, b); 
}, [a, b]);</code></pre><p>En este caso, <code>computeExpensiveValue</code> solo se recalculará cuando <code>a</code> o <code>b</code> cambien.</p><hr><h4 id="2-usecallback-memoriza-funciones"><strong>2. useCallback: Memoriza funciones</strong></h4><p><strong>¿Qué es?</strong></p><p><code>useCallback</code> se utiliza para memorizar funciones, evitando que React cree nuevas instancias de la función en cada renderizado. Esto es útil cuando pasamos funciones como props a componentes hijos, ya que previene que estos se re-rendericen innecesariamente.</p><p><strong>¿Por qué es importante?</strong></p><p>En React, cuando un componente padre se vuelve a renderizar, React considera que cualquier función definida dentro de ese componente es "nueva". Esto puede llevar a que los componentes hijos que reciben esa función como prop también se re-rendericen, incluso si no es necesario.</p><p><strong>Características clave:</strong></p><ul><li>Memoriza la referencia de una función.</li><li>Es útil en combinación con <code>React.memo</code>, que optimiza componentes para no renderizarse si sus props no cambian.</li><li>Acepta dos argumentos: una función y un arreglo de dependencias.</li></ul><p><strong>Ejemplo básico:</strong></p><pre><code>const memoizedCallback = useCallback(() =&gt; { 
	doSomething(a, b); 
}, [a, b]);</code></pre><p>Aquí, la función <code>doSomething</code> se volverá a definir únicamente si <code>a</code> o <code>b</code> cambian.</p><hr><h4 id="3-uselayouteffect-ajustes-visuales-antes-del-renderizado"><strong>3. useLayoutEffect: Ajustes visuales antes del renderizado</strong></h4><p><strong>¿Qué es?</strong></p><p><code>useLayoutEffect</code> es similar a <code>useEffect</code>, pero se ejecuta de manera síncrona después de que React ha realizado todas las modificaciones al DOM y antes de que el navegador pinte la pantalla. Esto permite realizar ajustes visuales inmediatos, como calcular dimensiones o reposicionar elementos.</p><p><strong>¿Cuándo usarlo?</strong></p><p>Se utiliza en situaciones donde necesitas leer o escribir directamente en el DOM y esos cambios deben reflejarse antes de que el usuario vea algo en pantalla.</p><p><strong>Características clave:</strong></p><ul><li>Se ejecuta antes del renderizado visual.</li><li>Es útil para medir dimensiones, realizar cálculos de posición o sincronizar animaciones.</li><li>Acepta dos argumentos: una función y un array de dependencias.</li></ul><p><strong>Ejemplo básico:</strong></p><pre><code>useLayoutEffect(() =&gt; { 
	const dimension = element.getBoundingClientRect();
    console.log(dimension); 
}, [element]);</code></pre><hr><h3 id="implementaci-n-pr-ctica"><strong>Implementación Práctica</strong></h3><p>Para fortalecer el conocimiento, vamos a aplicar estos hooks en una aplicación React, &nbsp;un ecommerce que hemos venido desarrollando. El objetivo es mejorar el rendimiento y la experiencia del usuario utilizando <code>useMemo</code>, <code>useCallback</code> y <code>useLayoutEffect</code>.</p><figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.49999999999999%;" class="fluid-width-video-wrapper">
            <iframe width="200" height="113" src="https://www.youtube.com/embed/295yPSj4y9I?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" title="Hooks Avanzados en React 🚀 | useMemo + useCallback + useLayoutEffect | Mejora Performance y UX" name="fitvid0"></iframe>
          </div>
        </div>
      </figure><p><strong>Contexto de la Aplicación:</strong></p><ul><li>Es un ecommerce que ya utiliza hooks como <code>useState</code> y <code>useEffect</code>.</li><li>Necesitamos agregar filtros y tooltips, los cuales pueden generar impacto en el performance.</li><li>Queremos optimizar cálculos, evitar renders innecesarios y ajustar elementos visuales dinámicamente.</li></ul><h4 id="1-aplicaci-n-de-usememo-filtros-din-micos"><strong>1. Aplicación de useMemo: Filtros dinámicos</strong></h4><p>En el componente <code>ProductFilters.jsx</code>, utilizamos <code>useMemo</code> para calcular las categorías y el rango de precios:</p><pre><code>const categories = useMemo(() =&gt; { 
	return [...new Set(products.map((product) =&gt; product.category))]; }, [products]); 
    
const priceRange = useMemo(() =&gt; { 
	const prices = products.map((product) =&gt; product.price); 
    return { min: Math.floor(Math.min(...prices)), 
    		 max: Math.ceil(Math.max(...prices)), 
    }; 
 }, [products]);</code></pre><p>Esto asegura que las categorías y los precios solo se recalculen cuando <code>products</code> cambie, mejorando el rendimiento en aplicaciones con grandes cantidades de datos.</p><hr><h4 id="2-aplicaci-n-de-usecallback-optimizaci-n-de-funciones"><strong>2. Aplicación de useCallback: Optimización de funciones</strong></h4><p>En <code>Home.jsx</code>, usamos <code>useCallback</code> para manejar cambios en los filtros:</p><pre><code>const handleCategoryChange = useCallback((category) =&gt; { 
	setCategory(category); 
}, []); 

const handlePriceChange = useCallback((price) =&gt; { 
	setMaxPrice(price); 
}, []);</code></pre><p>Esto evita que las funciones sean recreadas en cada renderizado, reduciendo renders innecesarios en el componente <code>ProductFilters</code>.</p><hr><h4 id="3-aplicaci-n-de-uselayouteffect-tooltips-ajustables"><strong>3. Aplicación de useLayoutEffect: Tooltips ajustables</strong></h4><p>En el componente <code>PriceTooltip.jsx</code>, utilizamos <code>useLayoutEffect</code> para ajustar la posición de un tooltip dentro del viewport:</p><pre><code>useLayoutEffect(() =&gt; { 
	if (showTooltip &amp;&amp; tooltipRef.current &amp;&amp; containerRef.current) { 
    	const containerRect = containerRef.current.getBoundingClientRect(); 
        const tooltipRect = tooltipRef.current.getBoundingClientRect(); 
        const newPosition = { 
        	top: -tooltipRect.height, 
        	left: containerRect.left + tooltipRect.width &gt; window.innerWidth ? -(tooltipRect.width - containerRect.width) : 0, 
        }; 
       	setTooltipPosition(newPosition); 
    } 
}, [showTooltip]);</code></pre><p>Esto asegura que los tooltips sean visibles y estén correctamente posicionados en todo momento.</p><hr><p>Al finalizar la implementación de filtros y mejoras visuales, usando la combinación de <code>useMemo</code>, <code>useCallback</code> y <code>useLayoutEffect</code>, logramos mejorar el perfomance de nuestra aplicación y en general estos hooks permiten crear aplicaciones React más rápidas, eficientes y visualmente atractivas. </p><p>Estos hooks avanzados no solo mejoran el rendimiento, sino que también optimizan la experiencia del usuario, llevándote un paso más allá en el desarrollo frontend. ¡Ponlos en práctica y observa cómo tus aplicaciones alcanzan un nuevo nivel!</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Cómo crear un Carrusel de imágenes con React y Framer Motion ]]>
                </title>
                <description>
                    <![CDATA[ Seguramente has visto muchos carruseles en aplicaciones actuales. Conocidos por muchos nombres como sliders o rotators, estos elementos web muestran contenido de manera visualmente atractiva, ya sea deslizándolo o rotándolo. Los carruseles sirven tanto para destacar tu Interfaz de Usuario como para ahorrar espacio y así ofrecer una buena experiencia ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/como-crear-un-carrusel-de-imagenes-con-react-y-framer-motion/</link>
                <guid isPermaLink="false">66f4465c7985df0457eb175c</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Framer Motion ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Constanza Areal ]]>
                </dc:creator>
                <pubDate>Fri, 18 Oct 2024 16:54:07 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2024/10/Screenshot-2023-06-26-at-10.45.34.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Seguramente has visto muchos carruseles en aplicaciones actuales. Conocidos por muchos nombres como <em>sliders</em> o <em>rotators</em>, estos elementos web muestran contenido de manera visualmente atractiva, ya sea deslizándolo o rotándolo.</p><p>Los carruseles sirven tanto para destacar tu Interfaz de Usuario como para ahorrar espacio y así ofrecer una buena experiencia de usuario.</p><p>Los carruseles se han convertido en un elemento fundamental en el diseño UI, frecuentemente usados para mostrar imágenes, testimonios de clientes, entre otros. Son indispensables al momento de crear una interfaz llamativa y dinámica.</p><p>En este artículo, nos sumergiremos en el proceso de crear un carrusel de imágenes con React y Framer Motion, guiándote por cada paso para crear un componente visual llamativo e interactivo para tu aplicación.</p><h2 id="-qu-es-framer-motion"><strong>¿Qué es Framer Motion?</strong></h2><p>Es una librería de animación de código abierto para aplicaciones React, que se utiliza para crear animaciones <em>responsive</em> y dinámicas para nuestra aplicación web. </p><p>Framer motion tiene muchas funcionalidades útiles, como por ejemplo:</p><ol><li> <em>Animation</em>: Esto permite hacer transiciones fluidas para tus componentes.</li><li><em>Gestures</em>: La librería ofrece soporte para movimientos táctiles y con el ratón. Esto permite dar cuenta de ciertos eventos.</li><li><em>Variants</em>: Framer motion permite declarar componentes de forma declarativa y así mantener tu código ordenado y reutilizable.</li></ol><p>Todas estas funcionalidades son muy útiles y pronto veremos cómo utilizarlas.</p><p>Para poder entender mejor cómo utilizar Framer Motion, se puede explorar su <a href="https://www.framer.com/motion/">documentación y recursos [en inglés]</a> aunque en este artículo nos vamos a concentrar en sus elementos fundamentales. Mientras los guío a través de los conceptos básicos de Framer Motion, mi objetivo principal es crear un carrusel atractivo e interactivo.</p><h2 id="c-mo-preparar-tu-entorno-de-desarrollo"><strong>Cómo preparar tu entorno de desarrollo</strong></h2><p>Lo primero que vamos a hacer es preparar nuestro entorno de desarrollo. Esto incluye instalar los paquetes necesarios para poder crear tu aplicación, como <a href="https://nodejs.dev/en/download/">Node.js</a> y <a href="https://www.npmjs.com/package/download">npm</a>. Si ya los tienes instalados, no es necesario que lo hagas de nuevo.</p><h3 id="crear-una-aplicaci-n-react"><strong>Crear una aplicación React</strong></h3><p>A esta altura, voy a asumir que ya tienes instalados Node y npm. Para crear una aplicación React, solo debes ir a tu terminal e ir a la ubicación donde quieras alojar tu aplicación. </p><p>Luego, ejecuta este comando:</p><pre><code class="language-bash">npx create-react-app react-image-carousel
</code></pre><p>Puedes elegir cualquier nombre para tu aplicación, pero a los fines de este artículo yo le pondré <code>react-image-carousel</code>.</p><p>Una vez que hayas creado exitosamente tu aplicación, abre la carpeta en tu editor de código. Deberías tener algunos archivos y estilos por defecto, además de verse así:</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/rC9qt5N.png" class="kg-image" alt="Image" width="1149" height="462" loading="lazy"></figure><p>Como no vamos a necesitar los siguientes archivos, puedes borrarlos: <code>app.test.js</code>, y <code>logo.svg</code>, and <code>reportWebVitals.js</code>, <code>setupTest.js</code>. También puedes borrar los estilos que vienen por defecto en <code>App.css</code>.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/3en8Ssk.png" class="kg-image" alt="Image" width="1032" height="359" loading="lazy"></figure><p>Ahora que tu aplicación React ya está creada y preparada, el último paso para terminar de preparar tu entorno de desarrollo es instalar Framer Motion. </p><p>Para hacer esto, simplemente ve a tu terminal y asegúrate que estés en la carpeta de tu proyecto, y luego ejecuta este comando:</p><pre><code class="language-bash"> npm install framer-motion
</code></pre><p>Esto debería instalar la última versión de Framer Motion. Con esto ya estarás listo. Solo ejecuta el comando <code>npm run start</code> e inicia el entorno de desarrollo en tu navegador.</p><h2 id="c-mo-dise-ar-el-componente-image-carousel">Cómo diseñar el componente Image Carousel</h2><p>Para empezar con el diseño, vamos a crear primero el componente <code>Carousel.js</code>. Dentro del componente Carousel, vamos a importar el <em>hook</em> <code>useState</code> de React, y luego las propiedades <code>motion</code> y <code>AnimatePresence</code> desde Framer Motion</p><pre><code class="language-javascript">import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
</code></pre><p>Luego creamos nuestra función Carousel que lleva la <em>prop</em> <code>images</code>, un <em>arreglo</em> con los enlaces de las imágenes:</p><pre><code class="language-javascript">const Carousel = ({ images }) =&gt; {};
</code></pre><p>En nuestra función Carousel, vamos a inicializar un variable de estado con <code>useState</code>. Para hacer un seguimiento del índice de imagen actual, usamos <code>setCurrentIndex</code>, que es la función para actualizar el índice.</p><p>Luego, creamos tres funciones <em>helper</em> que serán las encargadas de manejar las interacciones de usuario. Estas incluyen:</p><ul><li><code>handleNext</code>: Esto actualiza el <em><code>currentIndex</code></em> al siguiente índice y así poder cambiar la foto. Cuando llega al fin del <em>arreglo, </em>comienza de nuevo. </li><li><code>handlePrevious</code>: Hace lo mismo que <code>handleNext</code>, pero en orden inverso, lo que nos permite volver a una imagen.</li><li><code>handleDotClick</code>: Esta función lleva un índice como parámetro y actualiza <em><code>currentIndex</code></em>. Con esto podemos movernos entre las distintas imágenes solo haciendo clic en los puntos.</li></ul><pre><code class="language-javascript">const Carousel = ({ images }) =&gt; {
  const [currentIndex, setCurrentIndex] = useState(0);

  const handleNext = () =&gt; {
    setCurrentIndex((prevIndex) =&gt;
      prevIndex + 1 === images.length ? 0 : prevIndex + 1
    );
  };
  const handlePrevious = () =&gt; {
    setCurrentIndex((prevIndex) =&gt;
      prevIndex - 1 &lt; 0 ? images.length - 1 : prevIndex - 1
    );
  };
  const handleDotClick = (index) =&gt; {
    setCurrentIndex(index);
  };
</code></pre><p>Estas son las funciones <em>helper</em> que vamos a necesitar para nuestro componente.</p><h3 id="c-mo-crear-nuestra-plantilla"><strong>Cómo crear nuestra plantilla</strong></h3><p>Nuestra plantilla es bastante simple y consiste de nuestra imagen, el deslizador [slide_directon] y los puntos [indicator]</p><pre><code class="language-jsx">  return (
    &lt;div className="carousel"&gt;
        &lt;img
          key={currentIndex}
          src={images[currentIndex]}
        /&gt;&lt;div className="slide_direction"&gt;
        &lt;div className="left" onClick={handlePrevious}&gt;
          &lt;svg
            xmlns="http://www.w3.org/2000/svg"
            height="20"
            viewBox="0 96 960 960"
            width="20"
          &gt;
            &lt;path d="M400 976 0 576l400-400 56 57-343 343 343 343-56 57Z" /&gt;
          &lt;/svg&gt;
        &lt;/div&gt;
        &lt;div className="right" onClick={handleNext}&gt;
          &lt;svg
            xmlns="http://www.w3.org/2000/svg"
            height="20"
            viewBox="0 96 960 960"
            width="20"
          &gt;
            &lt;path d="m304 974-56-57 343-343-343-343 56-57 400 400-400 400Z" /&gt;
          &lt;/svg&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div className="indicator"&gt;
        {images.map((_, index) =&gt; (
          &lt;div
            key={index}
            className={`dot ${currentIndex === index ? "active" : ""}`}
            onClick={() =&gt; handleDotClick(index)}
          &gt;&lt;/div&gt;
        ))}
      &lt;/div&gt;
    &lt;/div&gt;
  );
</code></pre><p>Como pueden ver en esta plantilla, se muestra la imagen del índice actual. Luego, tenemos un <em><code>div</code></em> con la clase <code>slider_direction</code> que contiene otros dos <em><code>divs</code></em> con las clases <code>left</code> y <code>right</code>. Ambos serán los botones de navegación del carrusel y usan SVGs para mostrar los iconos de flechas. Sus funciones <em><code>onClick</code></em> están parametrizados con las funciones <code>handlePrevious</code> y <code>handleNext</code>, respectivamente.</p><p>También tenemos un <em><code>div indicator</code></em> que creamos para mostrar una serie de círculos que representan cada imagen del carrusel. La función recorre el <em>arreglo</em> de imágenes y crea un círculo para cada imagen. De esta forma, se parametriza la clase <code>active</code> para cada círculo al que le corresponda <code>currentIndex</code>.</p><p>Después agregamos un <code>onClick</code> para cada círculo que está parametrizado para hacer correr <code>handleDotClick</code> con el índice del círculo.</p><p>Por ahora, con esto ya armamos nuestra plantilla. Lo único que falta es exportar el componente Carousel, importarlo en el componente <code>App.js</code> y luego agregar algo de CSS. Luego de esto estaremos listos para empezar a animar.</p><p>Por eso, vamos a exportar nuestra función Carousel para nuestro componente <code>Carousel.js</code>.</p><pre><code class="language-jsx">export default Carousel;
</code></pre><h2 id="como-usar-el-component-carousel"><strong>Como usar el component Carousel</strong></h2><p>Ya hemos creado nuestro componente Carousel, pero para poder usarlo tenemos que importarlo dentro de nuestro componente <code>Apps.js</code>:</p><pre><code class="language-jsx">import Carousel from "./Carousel";
</code></pre><p>Después de hacer esto, ya podemos crear nuestro <em>arreglo </em>de imágenes en el que van a estar todos los enlaces a las imágenes:</p><pre><code class="language-js">const images = [
  "https://images.pexels.com/photos/169647/pexels-photo-169647.jpeg?auto=compress&amp;cs=tinysrgb&amp;w=600",
  "https://images.pexels.com/photos/313782/pexels-photo-313782.jpeg?auto=compress&amp;cs=tinysrgb&amp;w=1260&amp;h=750&amp;dpr=1",
  "https://images.pexels.com/photos/773471/pexels-photo-773471.jpeg?auto=compress&amp;cs=tinysrgb&amp;w=1260&amp;h=750&amp;dpr=1",
  "https://images.pexels.com/photos/672532/pexels-photo-672532.jpeg?auto=compress&amp;cs=tinysrgb&amp;w=1260&amp;h=750&amp;dpr=1",
  "https://images.pexels.com/photos/632522/pexels-photo-632522.jpeg?auto=compress&amp;cs=tinysrgb&amp;w=1260&amp;h=750&amp;dpr=1",
  "https://images.pexels.com/photos/777059/pexels-photo-777059.jpeg?auto=compress&amp;cs=tinysrgb&amp;w=1260&amp;h=750&amp;dpr=1",
];
</code></pre><p>Todas estas son imágenes que obtuve de <a href="https://www.pexels.com/">pexels</a> y las que vamos a usar en este &nbsp;proyecto.</p><p>Luego, agregamos nuestra función App que es donde va a estar la plantilla de nuestra aplicación:</p><pre><code class="language-jsx">function App() {
  return (
    &lt;div className="App"&gt;
      &lt;header className="App-header"&gt;
        &lt;h1&gt;Carrusel de imagenes usando React y Framer Motion&lt;/h1&gt;
      &lt;/header&gt;
      &lt;main&gt;
        &lt;Carousel images={images} /&gt;
      &lt;/main&gt;
    &lt;/div&gt;
  );
}
export default App;
</code></pre><p>Como puedes ver, en nuestro encabezado tenemos una descripción de lo que hace nuestra aplicación.</p><p>Luego, en la sección <code><em>main</em></code><em> </em>está nuestro componente, Carousel que lleva una <em>prop</em> del <em>arreglo</em> de imágenes. Si recuerdas, esta es la <em>prop </em> que usamos en ese componente para mostrar la imágenes.</p><p>Por último, exportamos nuestro componente <code>App</code> así lo podemos usar en nuestro archivo <code>index.js</code>.</p><p>Para verla sin estilos, ejecuta el comando <code>npm run start</code> y la aplicación debería verse así:</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/xN2meFY.gif" class="kg-image" alt="Image" width="600" height="411" loading="lazy"></figure><p>Horrible ¿verdad? Estoy de acuerdo contigo, pero con algunas pocas líneas de CSS vamos a transformarlo. Así que vamos.</p><h3 id="c-mo-agregar-css"><strong>Cómo agregar CSS</strong></h3><p>Como no quiero crear una hoja de estilo separada para el componente Carousel, voy a hacer todo el CSS en el archivo <code>App.css</code>. No te olvides de luego importar tu hoja de estilo.</p><pre><code class="language-jsx">import "./App.css"
</code></pre><p>Este es nuestro CSS:</p><pre><code class="language-css">@import url("https://fonts.googleapis.com/css2?family=Oswald:wght@600&amp;display=swap");
.App-header {
  font-size: 1rem;
  text-align: center;
  font-family: "Oswald", sans-serif;
  padding-bottom: 2rem;
}
.carousel-images {
  position: relative;
  border-radius: 10px;
  height: 400px;
  max-width: 650px;
  margin: auto;
  overflow: hidden;
}
.carousel-images img {
  width: 99%;
  height: 99%;
  border-radius: 8px;
  border: #ff00008e solid 2px;
}
.slide_direction {
  display: flex;
  justify-content: space-between;
}
.left,
.right {
  background-color: #fb666675;
  color: #fff;
  padding: 10px 8px 8px 13px;
  margin: 0 20px;
  border-radius: 50%;
  position: absolute;
  top: 0;
  bottom: 0;
  margin: auto 10px;
  height: 25px;
  width: 25px;
}
.left {
  left: 0;
}
.right {
  right: 0;
}
.carousel-indicator {
  margin-top: 20px;
  display: flex;
  justify-content: center;
  gap: 20px;
}
.dot {
  background-color: #333;
  width: 15px;
  height: 15px;
  border-radius: 50%;
}
.active {
  background-color: #fa2020;
}
</code></pre><p>Y este el resultado con nuestro CSS aplicado:</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/0CfxWn4.gif" class="kg-image" alt="Image" width="600" height="425" loading="lazy"></figure><p>Seguramente coincidirás conmigo que así luce mucho mejor, además de ya ser completamente funcional.</p><p>Pasemos ahora a agregar animación con Framer Motion así le agregamos un lindo efecto de deslizador.</p><h2 id="c-mo-agregar-animaci-n-al-componente-carousel"><strong>Cómo agregar animación al componente Carousel</strong></h2><p>Antes de empezar a animar con Framer Motion, hay algunos conceptos con los que deberás estar familiarizado porque los vamos a usar bastante dentro de esta sección. Estos conceptos incluyen:</p><ul><li><em>Variants</em> son un grupo de propiedades cuyo propósito es definir cómo un elemento debería aparecer o animarse. Es posible crear distintos <em>variants </em>que representen diferentes estados visuales o animaciones para un elemento, como por ejemplo <code>open</code>, <code>closed</code>, <code>hover</code> y del estilo</li><li><em>Initial</em> es el estado que tu objeto tendrá antes de que la animación empiece.</li><li><em>Animate</em> es el estado animado de tu objeto. Es tan simple como eso.</li></ul><p>Volviendo al proyecto, añadiremos nuestras animaciones al componente Carousel. Ya hemos importado las dos propiedades que vamos a necesitar – <code>motion</code> y <code>AnimatePresence</code>.</p><p>Voy a dividir esta sección en tres partes porque la animación la añadiremos a las tres partes de nuestro código: la imagen, la dirección del deslizador y el círculo que se usa como indicador.</p><h3 id="animaci-n-de-la-imagen"><strong>Animación de la imagen</strong></h3><p>Para animar la entrada y salida de una imagen, necesitamos envolver nuestro elemento <code>img</code> con un componente <code>AnimationPresence</code>. Esto nos permite agregar animación cuando una imagen entra o sale. Luego añadimos un <code>motion.</code> a nuestra etiqueta:</p><pre><code class="language-jsx"> &lt;AnimatePresence&gt;
  &lt;motion.img key={currentIndex} src={images[currentIndex]} /&gt;
&lt;/AnimatePresence&gt;;
</code></pre><p>Luego nos posicionamos fuera de nuestra plantilla y declaramos nuestras <em>variants</em>:</p><pre><code class="language-jsx">  const slideVariants = {
    hiddenRight: {
      x: "100%",
      opacity: 0,
    },
    hiddenLeft: {
      x: "-100%",
      opacity: 0,
    },
    visible: {
      x: "0",
      opacity: 1,
      transition: {
        duration: 1,
      },
    },
    exit: {
      opacity: 0,
      scale: 0.8,
      transition: {
        duration: 0.5,
      },
    },
  };
</code></pre><p>Como puedes ver, los <code>sliderVariants</code> tienen cuatro propiedades:</p><ul><li><em><code>hiddenRight</code></em>: esto parametriza la opacidad de la imagen en 0 y la ubica del lado derecho del contenedor.</li><li><em><code>hiddenLeft</code></em>: hace lo mismo que <em><code>hiddenRight</code></em> pero la ubica del lado izquierdo.</li><li><em><code>visible</code></em>: esta es la propiedad que dispara la animación desde donde esté la imagen al centro del contenedor.</li><li><em><code>exit</code>:</em> esta propiedad controla la salida de una imagen de la pantalla al mismo tiempo que otra imagen entra. </li></ul><p>Ahora que ya declaramos nuestros <code><em>variants</em></code><em>, </em>¿cómo podemos prever desde dónde la imagen va a entrar? Para esto necesitamos parametrizar un estado de dirección y que ese estado se actualice de acuerdo con cuál de los <code>slide_direction</code> hizo clic el usuario.</p><pre><code class="language-jsx">const [direction, setDirection] = useState('left');
</code></pre><p>Entonces, vamos a establecer que la imagen entre desde la izquierda. Es lo lógico dado que la primera imagen que se mostrará es la primera de la lista. Luego, en nuestra función <em><code>helper</code></em>, vamos a establecer la dirección de acuerdo con la dirección clikeada:</p><pre><code class="language-jsx">  const handleNext = () =&gt; {
    setDirection("right");
    setCurrentIndex((prevIndex) =&gt;
      prevIndex + 1 === images.length ? 0 : prevIndex + 1
    );
  };

  const handlePrevious = () =&gt; {
    setDirection("left");

    setCurrentIndex((prevIndex) =&gt;
      prevIndex - 1 &lt; 0 ? images.length - 1 : prevIndex - 1
    );
  };

  const handleDotClick = (index) =&gt; {
    setDirection(index &gt; currentIndex ? "right" : "left");
    setCurrentIndex(index);
  };
</code></pre><p>Quizás hayas notado que no solo parametrizamos el estado para &nbsp;<code>handleNext</code> y <code>handlePrevious</code>. También lo hicimos para <code>handleDotClick</code>. Así cuando se clickea un círculo anterior o siguiente, la dirección se actualizará correctamente.</p><p>Pero recuerda: la función de <em>direction</em> es la de establecer el estado inicial de la imagen para que el deslizador funcione correctamente.</p><p>Ya parametrizado <em>direction</em>, vamos a usar nuestros <em><code>variants</code></em> con nuestro elemento <code>img</code>:</p><pre><code class="language-jsx">&lt;AnimatePresence&gt;
          &lt;motion.img
            key={currentIndex}
            src={images[currentIndex]}
            variants={slideVariants}
            initial={direction === "right" ? "hiddenRight" : "hiddenLeft"}
            animate="visible"
            exit="exit"
          /&gt;
        &lt;/AnimatePresence&gt;
</code></pre><p>Agregamos la <em>prop </em><code>variants</code> y la parametrizamos igual que a los <code>slideVariants</code> que creamos. Luego, agregamos la <em>prop </em><code>initial</code>, con la misma sintaxis que un operador ternario. Esto establece que el estado inicial de la imagen sea <code>hiddenRight</code> o <code>hiddenLeft</code> cuando el usuario hace clic en el círculo indicador de la imagen o en el <code>slider_direction</code>.</p><p>Después de esto, agregamos la propiedad <em><code>animate</code></em> que se encarga de animar la imagen desde su posición inicial hasta la posición establecida en la propiedad <code>visible</code>.</p><p>Por último, agregamos nuestra propiedad <em><code>exit</code></em> y la establecemos en <em><code>exit</code>.</em> Esto va a animar la imagen para que salga de la nueva pantalla cuando entra una nueva.</p><p>Hay muchas <em>props</em> que puede usar con Framer Motion. Puedes leer la <a href="https://www.framer.com/motion/component/#props">documentación [en inglés] </a>para aprender más sobre ellas. </p><p>Ya con esto, nuestra carrusel de imágenes debería funcionar correctamente.</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/4VNWzsq.gif" class="kg-image" alt="Image" width="600" height="425" loading="lazy"></figure><h3 id="animaci-n-del-deslizador-y-de-los-c-rculos-indicadores-de-la-imagen"><strong>Animación del deslizador y de los círculos indicadores de la imagen</strong></h3><p>Podríamos detenernos aquí, pero quiero agregar un poco de animación a las direcciones del deslizador y a los los círculos indicadores de la imagen.</p><pre><code class="language-jsx">  const slidersVariants = {
    hover: {
      scale: 1.2,
      backgroundColor: "#ff00008e",
    },
  }; 
const dotsVariants = {
    initial: {
      y: 0,
    },
    animate: {
      y: -10,
      scale: 1.3,
      transition: { type: "spring", stiffness: 1000, damping: "10" },
    },
    hover: {
      scale: 1.1,
      transition: { duration: 0.2 },
    },
  };
</code></pre><p>Empezamos creando nuestros <em>variants. </em>Para los <code>slidersVariants</code>, una propiedad <em>hover</em>. Para los <code>dotsVariants</code> tenemos tres propiedades: <em><code>initial</code></em>, <em><code>animate</code> </em>y <em><code>hover</code>.</em></p><p>Así como lo hicimos como en el elemento <code>img</code>, para poder usar Framer Motion vamos a agregar <code>motion.</code> como prefijo al nombre del elemento.</p><pre><code class="language-jsx">&lt;div className="slide_direction"&gt;
  &lt;motion.div
    variants={slidersVariants}
    whileHover="hover"
    className="left"
    onClick={handlePrevious}
  &gt;
    &lt;svg
      xmlns="http://www.w3.org/2000/svg"
      height="20"
      viewBox="0 96 960 960"
      width="20"
    &gt;
      &lt;path d="M400 976 0 576l400-400 56 57-343 343 343 343-56 57Z" /&gt;
    &lt;/svg&gt;
  &lt;/motion.div&gt;
  &lt;motion.div
    variants={slidersVariants}
    whileHover="hover"
    className="right"
    onClick={handleNext}
  &gt;
    &lt;svg
      xmlns="http://www.w3.org/2000/svg"
      height="20"
      viewBox="0 96 960 960"
      width="20"
    &gt;
      &lt;path d="m304 974-56-57 343-343-343-343 56-57 400 400-400 400Z" /&gt;
    &lt;/svg&gt;
  &lt;/motion.div&gt;
&lt;/div&gt;;
</code></pre><p>Como pueden ver, hemos añadido nuestros <em><code>variants</code></em> y los hemos parametrizados igual a <code>slidersVariants</code>. Luego, usamos una nueva propiedad <code>whileHover</code> y la parametrizamos igual que a la propiedad <em><code>over</code></em> que especificamos en nuestro objeto <code>slidersVariants</code>.</p><pre><code class="language-jsx">&lt;motion.div
  key={index}
  className={`dot ${currentIndex === index ? "active" : ""}`}
  onClick={() =&gt; handleDotClick(index)}
  initial="initial"
  animate={currentIndex === index ? "animate" : ""}
  whileHover="hover"
  variants={dotsVariants}
&gt;&lt;/motion.div&gt;;
</code></pre><p>Aquí no solo agregamos una propiedad <em><code>whileHover</code></em>. También agregamos la propiedad <code>initial</code> y la propiedad <code>animate</code> que anima el círculo de la imagen seleccionada así se destaca.</p><p>En nuestro objeto <code>slidersVariants</code>, hemos agregado una transición a la animación que hace que haya un pequeño salto cuando ocurre la transición. </p><p>Con todo esto junto, nos queda un carrusel de imágenes prolijo. Aquí el resultado final:</p><figure class="kg-card kg-image-card"><img src="https://i.imgur.com/Bgghl7M.gif" class="kg-image" alt="Image" width="600" height="425" loading="lazy"></figure><p>Para referencia, aquí tienes el código completo del componente carrusel:</p><pre><code class="language-jsx">import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";

const Carousel = ({ images }) =&gt; {
  const [currentIndex, setCurrentIndex] = useState(0);
  const [direction, setDirection] = useState(null);

  const slideVariants = {
    hiddenRight: {
      x: "100%",
      opacity: 0,
    },
    hiddenLeft: {
      x: "-100%",
      opacity: 0,
    },
    visible: {
      x: "0",
      opacity: 1,
      transition: {
        duration: 1,
      },
    },
    exit: {
      opacity: 0,
      scale: 0.8,
      transition: {
        duration: 0.5,
      },
    },
  };
  const slidersVariants = {
    hover: {
      scale: 1.2,
      backgroundColor: "#ff00008e",
    },
  };
  const dotsVariants = {
    initial: {
      y: 0,
    },
    animate: {
      y: -10,
      scale: 1.2,
      transition: { type: "spring", stiffness: 1000, damping: "10" },
    },
    hover: {
      scale: 1.1,
      transition: { duration: 0.2 },
    },
  };

  const handleNext = () =&gt; {
    setDirection("right");
    setCurrentIndex((prevIndex) =&gt;
      prevIndex + 1 === images.length ? 0 : prevIndex + 1
    );
  };

  const handlePrevious = () =&gt; {
    setDirection("left");

    setCurrentIndex((prevIndex) =&gt;
      prevIndex - 1 &lt; 0 ? images.length - 1 : prevIndex - 1
    );
  };

  const handleDotClick = (index) =&gt; {
    setDirection(index &gt; currentIndex ? "right" : "left");
    setCurrentIndex(index);
  };

  return (
    &lt;div className="carousel"&gt;
        &lt;div className="carousel-images"&gt;
        &lt;AnimatePresence&gt;
          &lt;motion.img
            key={currentIndex}
            src={images[currentIndex]}
            initial={direction === "right" ? "hiddenRight" : "hiddenLeft"}
            animate="visible"
            exit="exit"
            variants={slideVariants}
          /&gt;
        &lt;/AnimatePresence&gt;
        &lt;div className="slide_direction"&gt;
          &lt;motion.div
            variants={slidersVariants}
            whileHover="hover"
            className="left"
            onClick={handlePrevious}
          &gt;
            &lt;svg
              xmlns="http://www.w3.org/2000/svg"
              height="20"
              viewBox="0 96 960 960"
              width="20"
            &gt;
              &lt;path d="M400 976 0 576l400-400 56 57-343 343 343 343-56 57Z" /&gt;
            &lt;/svg&gt;
          &lt;/motion.div&gt;
          &lt;motion.div
            variants={slidersVariants}
            whileHover="hover"
            className="right"
            onClick={handleNext}
          &gt;
            &lt;svg
              xmlns="http://www.w3.org/2000/svg"
              height="20"
              viewBox="0 96 960 960"
              width="20"
            &gt;
              &lt;path d="m304 974-56-57 343-343-343-343 56-57 400 400-400 400Z" /&gt;
            &lt;/svg&gt;
          &lt;/motion.div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div className="carousel-indicator"&gt;
        {images.map((_, index) =&gt; (
          &lt;motion.div
            key={index}
            className={`dot ${currentIndex === index ? "active" : ""}`}
            onClick={() =&gt; handleDotClick(index)}
            initial="initial"
            animate={currentIndex === index ? "animate" : ""}
            whileHover="hover"
            variants={dotsVariants}
          &gt;&lt;/motion.div&gt;
        ))}
      &lt;/div&gt;
    &lt;/div&gt;
  );
};
export default Carousel;
</code></pre><p>Dale una mirada al repositorio en <a href="https://github.com/Cejay101/ImageCarousel">GitHub</a>.</p><p>Aquí está el sitio en <a href="https://image-carousel-cj.netlify.app/">Netlify</a>.</p><p><em>Solo para que estén al tanto, este código tiene algunos problemas de accesibilidad por lo que no debería ser usado en un ambiente de producción.</em></p><h2 id="recursos"><strong>Recursos</strong></h2><p>Entiendo que puede haber algunos términos o sintaxis que no estén del todo claro, sobretodo si eres nuevo a React o usando Framer Motion. Por eso, aquí tienes algunos recursos que recomiendo si quieres aprender más:</p><ul><li><a href="https://es.react.dev/">Documentación de React</a> </li><li><a href="https://www.framer.com/motion/">Framer Motion Documentation</a> [en inglés]</li><li><a href="https://www.youtube.com/playlist?list=PL4cUxeGkcC9iHDnQfTHEVVceOEBsOf07i">Framer Motion Course</a> [en inglés]</li></ul><h2 id="conclusi-n"><strong>Conclusión</strong></h2><p>En este artículo, vimos el proceso de diseñar un carrusel de imágenes <em>responsive </em>e interactivo, combinando React y Framer Motion, una librería de animación.</p><p>Al incorporar componentes como <code>motion</code> y <code>AnimationPresence</code>, fuimos paso a paso costruyendo un carrusel visualmente atractivo, que muestra nuestras imagenes y con transiciones impecables y animaciones interesantes para una experiencia de usuario enriquecedora.</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Curso ChatGPT React: Codifica tu propio clon de ChatGPT ]]>
                </title>
                <description>
                    <![CDATA[ La API OpenAI es una potente herramienta que permite a los desarrolladores aprovechar el poder de los algoritmos avanzados de aprendizaje automático para crear aplicaciones inteligentes. Acabamos de publicar un curso en el canal de YouTube de freeCodeCamp.org que te enseñará a utilizar la API OpenAI con React para crear ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/curso-chatgpt-react-codifica-tu-propio-clon-de-chatgpt/</link>
                <guid isPermaLink="false">67104bb9408801043e04b55e</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rafael D. Hernandez ]]>
                </dc:creator>
                <pubDate>Wed, 16 Oct 2024 23:31:11 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2024/10/chatgptclone.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Artículo original:</strong> <a href="https://www.freecodecamp.org/news/chatgpt-react-course/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">ChatGPT React Course – Code Your Own ChatGPT Clone</a>
      </p><p>La API OpenAI es una potente herramienta que permite a los desarrolladores aprovechar el poder de los algoritmos avanzados de aprendizaje automático para crear aplicaciones inteligentes.</p><p>Acabamos de publicar un curso en el canal de YouTube de freeCodeCamp.org que te enseñará a utilizar la API OpenAI con React para crear una potente aplicación capaz de responder preguntas, traducir texto, convertir código de un idioma a otro y mucho más.</p><p>Nishant Singh ha desarrollado este curso. Nishant ha creado muchos tutoriales populares basados en texto y vídeo.</p><p>El curso está diseñado para desarrolladores con experiencia en React, pero no se requiere experiencia previa con la API OpenAI. El curso comienza introduciendo los conceptos básicos de la API OpenAI, incluyendo cómo configurar su cuenta y autenticarse con la API.</p><p>A continuación, aprenderá a utilizar la API OpenAI para responder preguntas, utilizando el modelo GPT-3. Aprenderás cómo enviar solicitudes a la API y recuperar los resultados, y cómo mostrar los resultados en tu aplicación React.</p><p>A continuación, aprenderás a utilizar la API OpenAI para traducir texto. Aprenderás a enviar texto a la API y a recuperar el texto traducido. También aprenderás a mostrar el texto traducido en tu aplicación React.</p><p>A continuación, aprenderás a utilizar la API OpenAI para convertir código de un idioma a otro. Aprenderás cómo enviar código a la API y recuperar el código convertido, y cómo mostrar el código convertido en tu aplicación React.</p><p>Por último, aprenderás a crear una aplicación completa utilizando la API OpenAI y React. Aprenderás a utilizar los conceptos y técnicas que has aprendido a lo largo del curso para crear una aplicación funcional que pueda responder preguntas, traducir texto y convertir código.</p><p>Al final de este curso, tendrás una sólida comprensión de cómo utilizar la API OpenAI con React para crear aplicaciones potentes. Podrás utilizar los conocimientos adquiridos para crear tus propias aplicaciones con funcionalidades similares a ChatGPT.</p><p>Ve el curso completo <a href="https://youtu.be/98bGwOYoEGg">en el canal de YouTube de freeCodeCamp.org (1 hora).</a></p><figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.49999999999999%;" class="fluid-width-video-wrapper">
            <iframe width="200" height="113" src="https://www.youtube.com/embed/98bGwOYoEGg?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" title="ChatGPT Clone – OpenAI API and React Tutorial" name="fitvid0"></iframe>
          </div>
        </div>
      </figure> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Cómo recuperar datos en React: hoja de trucos + ejemplos ]]>
                </title>
                <description>
                    <![CDATA[ Hay muchas formas de recuperar datos de una API externa en React. Pero, ¿cuál debería utilizar para sus aplicaciones en 2021? En este tutorial, revisaremos cinco de los patrones más utilizados para recuperar datos con React realizando una solicitud HTTP a una API REST. No solo cubriremos cómo recuperar datos, ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/como-recuperar-datos-en-react-hoja-de-trucos-ejemplos/</link>
                <guid isPermaLink="false">66ace90684629203fb40d987</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Juan C. Guaña ]]>
                </dc:creator>
                <pubDate>Tue, 13 Aug 2024 15:33:39 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2024/08/how-to-fetch-data-in-react.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Artículo original:</strong> <a href="https://www.freecodecamp.org/news/fetch-data-react/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Fetch Data in React: Cheat Sheet + Examples</a>
      </p><p>Hay muchas formas de recuperar datos de una API externa en React. Pero, ¿cuál debería utilizar para sus aplicaciones en 2021?</p><p>En este tutorial, revisaremos cinco de los patrones más utilizados para recuperar datos con React realizando una solicitud HTTP a una API REST.</p><p>No solo cubriremos cómo recuperar datos, sino también cómo manejar mejor la carga y el estado de error al recuperar nuestros datos.</p><p>¡Empecemos!</p><blockquote>Para todos estos ejemplos, usaremos un endpoint del popular JSON Placeholder API, pero puedes usar su propia API que haya creado (como una API de Node con Express) o cualquier otra API pública.</blockquote><h3></h3><h2 id="1-c-mo-recuperar-datos-en-react-usando-la-api-fetch"><strong>1. Cómo Recuperar Datos en React Usando la API Fetch</strong></h2><p>La forma más accesible de recuperar datos con React es utilizar la API Fetch.</p><p>Fetch API es una herramienta integrada en la mayoría de los navegadores modernos en el objeto window ( <code>window.fetch</code>) y nos permite realizar solicitudes HTTP muy fácilmente utilizando promesas de JavaScript.</p><p>Para realizar una solicitud GET simple con fetch solo necesitamos incluir la URL del endpoint al que queremos realizar nuestra solicitud. Queremos realizar esta solicitud una vez que nuestro componente React se haya montado.</p><p>Para hacerlo, realizamos nuestra solicitud dentro del hook useEffect y nos aseguramos de proporcionar un arreglo de dependencias vacía como segundo argumento, de modo que nuestra solicitud solo se realice una vez (asumiendo que no depende de ningún otro dato en nuestro componente).</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/03/clip-1-fetch-min.gif" class="kg-image" alt="clip-1-buscar-min" width="600" height="400" loading="lazy"></figure><p>En el primer callback <code>.then()</code>, verificamos si la respuesta fue correcta ( <code>response.ok</code>). Si es así, devolvemos nuestra respuesta para pasar a la siguiente, luego volvemos a llamar como datos JSON, ya que esos son los datos que obtendremos de nuestra API de usuario aleatorio.</p><p>Si no es una respuesta correcta, asumimos que hubo un error al realizar la solicitud. Al usar fetch, necesitamos manejar los errores nosotros mismos, por lo que lo arrojamos <code>response</code>como un error para que nuestro callback <code>catch</code> lo maneje.</p><p>Aquí, en nuestro ejemplo, colocamos nuestros datos de error en el estado con <code>setError</code>. Si hay un error, devolvemos el texto "¡Error!".</p><blockquote>Tenga en cuenta que también puede mostrar un mensaje de error del objeto de error que pusimos en estado usando <code>error.message</code>.</blockquote><p>Usamos el callback <code>.finally()</code> como función que se llama cuando nuestra promesa se resolvió exitosamente o no. En él, lo configuramos <code>loading</code>en falso, para que ya no veamos nuestro texto de carga.</p><p>En su lugar, vemos nuestros datos en la página si la solicitud se realizó correctamente o si hubo un error al realizar la solicitud.</p><h2 id="2-c-mo-recuperar-datos-en-react-usando-axios"><strong>2. Cómo Recuperar Datos en React Usando Axios</strong></h2><p>El segundo enfoque para realizar solicitudes con React es utilizar la librería <code>axios</code>.</p><p>En este ejemplo, simplemente revisaremos nuestro ejemplo de Fetch instalando primero <code>axios</code>usando npm:</p><pre><code class="language-bash">npm install axios</code></pre><p>Luego lo importaremos en la parte superior de nuestro archivo de componente.</p><p>Lo que axios nos permite hacer es usar exactamente la misma sintaxis de promesa que fetch, pero en lugar de usar nuestra devolución de llamada primero y luego para determinar manualmente si la respuesta es correcta y arrojar un error, axios se encarga de eso por nosotros.</p><p>Además, nos permite en esa primera devolución de llamada obtener los datos JSON de <code>response.data</code>.</p><p>Lo conveniente de usar axios es que tiene una sintaxis mucho más corta que nos permite reducir nuestro código e incluye muchas herramientas y características que Fetch no tiene en su API.</p><p>Todas estas razones son las que se han convertido en la librería HTTP de referencia para los desarrolladores de React.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/03/clip-2-axios-min.gif" class="kg-image" alt="clip-2-axios-min" width="600" height="400" loading="lazy"></figure><h2 id="3-c-mo-recuperar-datos-en-react-usando-la-sintaxis-async-await"><strong>3. Cómo recuperar datos en React Usando la Sintaxis async/await</strong></h2><p>En ES7, fue posible resolver promesas utilizando la sintaxis <code>async / await</code>.</p><p>El beneficio de esto es que nos permite eliminar los callback <code>.then()</code>, <code>.catch()</code>y <code>.finally()</code> simplemente recupera nuestros datos resueltos asincrónicamente como si estuviéramos escribiendo código síncrono sin ninguna promesa.</p><p>En otras palabras, no tenemos que depender de devoluciones de llamada cuando usamos async/await con React.</p><p>Tenemos que ser conscientes del hecho de que cuando usamos <code>useEffect</code>, la función de efecto (el primer argumento) no puede convertirse en una <code>async</code> función.</p><p>Si echamos un vistazo al error de linting que nos da React si estábamos usando Create React App para construir nuestro proyecto, se nos dirá que esta función no puede ser asincrónica para evitar condiciones de carrera.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/03/clip-3-async-await-min.gif" class="kg-image" alt="clip-3-async-espera-min" width="600" height="400" loading="lazy"></figure><p>Como resultado, en lugar de hacer que esa función sea asíncrona, simplemente podemos crear una función asíncrona separada en nuestro componente, a la que podemos llamar sincrónicamente. Es decir, sin la palabra clave <code>await</code> &nbsp;delante.</p><p>En este ejemplo, creamos una función asíncrona llamada <code>getData</code>. Al llamarlo sincrónicamente dentro de useEffect, podemos recuperar nuestros datos como esperábamos.</p><h2 id="4-c-mo-recuperar-datos-en-react-usando-un-hook-de-react-personalizado-usefetch-"><strong>4. Cómo Recuperar Datos en React Usando un Hook de React Personalizado (useFetch)</strong></h2><p>Con el tiempo, es posible que te des cuenta de que se vuelve un poco tedioso y lleva mucho tiempo seguir escribiendo el hook useEffect con todo su texto estándar dentro de cada componente en el que desea recuperar datos.</p><p>Para reducir nuestro código reutilizado, podemos usar un hook personalizado como una abstracción especial, que podemos escribir nosotros mismos desde una librería de terceros (como estamos aquí, usando la librería <code>react-fetch-hook</code>).</p><p>Un hook personalizado que realiza nuestra solicitud HTTP nos permite hacer que nuestros componentes sean mucho más concisos. Todo lo que tenemos que hacer es llamar a nuestro hook en la parte superior de nuestro componente.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/03/clip-4-usefetch-min.gif" class="kg-image" alt="clip-4-usefetch-min" width="600" height="400" loading="lazy"></figure><p>En este caso, recuperamos todos los datos, la carga y el estado de error que necesitamos para poder usar la misma estructura para nuestro componente que antes, pero sin tener que hacerlo con <code>useEffect</code>. Además, ya no necesitamos escribir imperativamente cómo resolver nuestra promesa de nuestra solicitud GET cada vez que queremos realizar una solicitud.</p><h2 id="5-c-mo-recuperar-datos-en-react-usando-la-librer-a-react-query"><strong>5. Cómo Recuperar Datos en React Usando la L</strong>ibrería<strong> React Query</strong></h2><p>El uso de hooks personalizados es una excelente manera de escribir solicitudes HTTP mucho más concisas para obtener nuestros datos y todo su estado relacionado. Pero una librería que realmente lleva la recuperación de datos con hooks al siguiente nivel es React Query.</p><p>React Query no solo nos permite usar hooks personalizados que podemos reutilizar en nuestros componentes de manera concisa, sino que también nos brinda una gran cantidad de herramientas de administración de estado para poder controlar cuándo, cómo y con qué frecuencia se obtienen nuestros datos.</p><p>En particular, React query nos proporciona un caché, que puede ver a continuación a través de React Query Devtools. Esto nos permite gestionar fácilmente las solicitudes que hemos realizado según el valor clave que especificamos para cada solicitud.</p><p>Para las solicitudes siguientes, nuestra consulta de datos de usuario aleatorios se identifica mediante la cadena 'random-user' (proporcionada como primer argumento de <code>useQuery</code>).</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2021/03/clip-5-react-query-min.gif" class="kg-image" alt="clip-5-reaccionar-consulta-min" width="600" height="400" loading="lazy"></figure><p>Al hacer referencia a esa clave, podemos hacer cosas poderosas como recuperar, validar o restablecer nuestras diversas consultas.</p><blockquote>Si confiamos en nuestra solución de hook personalizada o useEffect, recuperaremos nuestros datos cada vez que se monte nuestro componente. En la mayoría de los casos, hacer esto es innecesario. Si nuestro estado externo no ha cambiado, idealmente no deberíamos tener que mostrar el estado de carga cada vez que mostramos nuestro componente.</blockquote><p>React Query mejora enormemente nuestra experiencia de usuario al intentar servir nuestros datos desde su caché primero y luego actualizar los datos en segundo plano para mostrar los cambios si el estado de nuestra API ha cambiado.</p><p>También nos brinda un arsenal de herramientas poderosas para administrar mejor nuestras solicitudes de acuerdo con cómo cambian nuestros datos a través de nuestra solicitud.</p><p>Por ejemplo, si nuestra aplicación nos permitió agregar un usuario diferente, es posible que queramos recuperar esa consulta una vez que se agregó el usuario. Si supiéramos que la consulta se cambia con mucha frecuencia, es posible que deseemos especificar que se actualice aproximadamente cada minuto. O para actualizarse cada vez que el usuario enfoque la pestaña de su ventana.</p><p>En resumen, React Query es la solución ideal no solo para realizar solicitudes de manera concisa, sino también para administrar de manera eficiente y efectiva los datos que se devuelven para nuestras solicitudes HTTP en todos los componentes de nuestra aplicación.</p><h2 id="-quiere-conservar-esta-gu-a-para-consultarla-en-el-futuro"><strong>¿Quiere conservar esta guía para consultarla en el futuro?</strong></h2><p>Aquí hay 3 ganancias rápidas que obtienes cuando obtienes la versión descargable:</p><ul><li>Obtendrá toneladas de fragmentos de código copiables para reutilizarlos fácilmente en sus propios proyectos.</li><li>Es una excelente guía de referencia para fortalecer tus habilidades como desarrollador de React y para entrevistas de trabajo.</li><li>Puede llevar, utilizar, imprimir, leer y releer esta guía literalmente donde quiera.</li></ul><h2 id="convi-rtete-en-un-desarrollador-profesional-de-react"><strong>Conviértete en un Desarrollador Profesional de React</strong></h2><p>React es difícil. No deberías tener que resolverlo tú mismo.</p><p>He reunido todo lo que sé sobre React en un solo curso para ayudarte a alcanzar tus objetivos en un tiempo récord:</p><p><strong><strong><a href="https://www.thereactbootcamp.com/">Presentamos: El Bootcamp de React</a></strong></strong></p><p><strong><strong>Es el único curso que desearía tener cuando comencé a aprender React.</strong></strong></p><p>Haga clic a continuación para probar React Bootcamp usted mismo:</p><figure class="kg-card kg-image-card"><img src="https://reedbarger.nyc3.digitaloceanspaces.com/reactbootcamp/react-bootcamp-cta-alt.png" class="kg-image" alt="Haga clic para unirse al Bootcamp de React" width="600" height="400" loading="lazy"></figure><p><br><em><em><a href="https://www.thereactbootcamp.com/">Haga clic para comenzar</a></em></em></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Cómo hacer un menú acordeón en React, desde cero y sin librerías externas ]]>
                </title>
                <description>
                    <![CDATA[ Los menús acordeón tienen varios usos como, por ejemplo, mostrar una lista de preguntas frecuentes, o menús y sub-menús, o también las distintas sucursales de un empresa, etc. En este artículo, veremos cómo hacer un menú acordeón en React, completamente desde cero, paso por paso y sin usar librerías externas. ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/como-hacer-un-menu-acordion/</link>
                <guid isPermaLink="false">669daca52bfc9803fe184649</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Constanza Areal ]]>
                </dc:creator>
                <pubDate>Tue, 30 Jul 2024 11:59:28 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2024/07/604df0d628094f59be2558d6.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Los menús acordeón tienen varios usos como, por ejemplo, mostrar una lista de preguntas frecuentes, o menús y sub-menús, o también las distintas sucursales de un empresa, etc.</p><p>En este artículo, veremos cómo hacer un menú acordeón en React, completamente desde cero, paso por paso y sin usar librerías externas.</p><p>Usaremos la sintaxis de React Hooks para hacer esta aplicación en React. Por lo que si no estás muy familiarizado con React Hooks, puedes dar un vistazo a mi artículo <a href="https://levelup.gitconnected.com/an-introduction-to-react-hooks-50281fd961fe?source=friends_link&amp;sk=89baff89ec8bc637e7c13b7554904e54">Introducción a React Hooks</a> [en inglés] para aprender los fundamentos de Hooks.</p><p><strong>Puedes ver la demo de la aplicación <a href="https://react-accordion-demo.netlify.app/">aquí</a>.</strong></p><p>Empecemos.</p><h2 id="configuraci-n-inicial-del-proyecto"><strong>Configuración inicial del proyecto</strong></h2><p>Crea un nuevo proyecto <code>create-react-app</code>.</p><pre><code class="language-javascript">npx create-react-app react-accordion-demo
</code></pre><p>Una vez que el proyecto está creado, borra todos los archivos de la carpeta <code>src</code> y crea los archivos <code>index.js</code>, <code>App.js</code> y <code>styles.css</code> dentro de ella. También crea aquí una nueva carpeta llamada <code>utils</code>.</p><p> Abre el archivo <code>styles.css</code> y copia <a href="https://github.com/myogeshchavan97/react-accordion-demo/blob/master/src/styles.css">este código</a> en ese archivo.</p><h2 id="c-mo-crear-las-primeras-p-ginas"><strong>Cómo crear las primeras páginas</strong></h2><p>Abre el archivo <code>src/App.js</code> y añade el siguiente código dentro de él:</p><pre><code class="language-jsx">import React from 'react';

const App = () =&gt; {
  const accordionData = {
    title: 'Section 1',
    content: `Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis sapiente
      laborum cupiditate possimus labore, hic temporibus velit dicta earum
      suscipit commodi eum enim atque at? Et perspiciatis dolore iure
      voluptatem.`
  };

  const { title, content } = accordionData;

  return (
    &lt;React.Fragment&gt;
      &lt;h1&gt;React Accordion Demo&lt;/h1&gt;
      &lt;div className="accordion"&gt;
        &lt;div className="accordion-item"&gt;
          &lt;div className="accordion-title"&gt;
            &lt;div&gt;{title}&lt;/div&gt;
            &lt;div&gt;+&lt;/div&gt;
          &lt;/div&gt;
          &lt;div className="accordion-content"&gt;{content}&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/React.Fragment&gt;
  );
};

export default App;
</code></pre><p>Aquí usamos las propiedades del objeto <code>accordionData</code> para mostrar el contenido del acordeón.</p><p>Para la propiedad <code>content</code>, usamos la sintaxis de la plantilla literal de <strong>ES6</strong> (``) así podemos escribir en varias líneas. También usamos un texto <em>lorem ipsum </em>de ejemplo.</p><p> Luego, abre el archivo <code>src/index.js</code> y añade el siguiente contenido:</p><pre><code class="language-jsx">import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './styles.css';

ReactDOM.render(&lt;App /&gt;, document.getElementById('root'));
</code></pre><p>Si ejecutas la aplicación usando el comando <code>yarn start</code> desde la terminal, verás la siguiente pantalla:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://gist.github.com/myogeshchavan97/98ae4f4ead57fde8d47fcf7641220b72/raw/92ab4961521a20180285b6fdf5179b6d17fbcdff/accordion_initial.png" class="kg-image" alt="Initial Accordion" width="600" height="400" loading="lazy"></figure><h2 id="c-mo-abrir-y-cerrar-el-men-acorde-n"><strong>Cómo abrir y cerrar el menú acordeón</strong></h2><p>Como has podido ver arriba, pudimos mostrar un única sección como parte del menú acordeón. Por defecto el acordeón está abierta, pero no podemos cerrarlo. Así que vamos a agregar esa funcionalidad para abrirlo y cerrarlo.</p><p>Añade un nuevo estado dentro del componente como se muestra a continuación:</p><pre><code class="language-js">const [isActive, setIsActive] = useState(false);
</code></pre><p> Aquí hemos definido el estado <code>isActive</code>. Con esto, ocultaremos o mostraremos el contenido del acordeón.</p><p>También importa el <em>hook</em> <code>useState</code> al comienzo del archivo:</p><pre><code class="language-js">import React, { useState } from 'react';
</code></pre><p>Luego, para el <code>div</code> con la clase <code>accordion-title</code>, añade el <em>handler </em><code>onClick</code> de la siguiente manera:</p><pre><code class="language-jsx">&lt;div className="accordion"&gt;
  &lt;div className="accordion-item"&gt;
    &lt;div
      className="accordion-title"
      onClick={() =&gt; setIsActive(!isActive)}
    &gt;
      &lt;div&gt;{title}&lt;/div&gt;
      &lt;div&gt;{isActive ? '-' : '+'}&lt;/div&gt;
    &lt;/div&gt;
    {isActive &amp;&amp; &lt;div className="accordion-content"&gt;{content}&lt;/div&gt;}
  &lt;/div&gt;
&lt;/div&gt;
</code></pre><p>Aquí estamos invirtiendo el valor del estado <code>isActive</code> cuando hacemos clic en el div <code>accordion-title</code>. Si el valor de <code>isActive</code> es <code>false</code>, lo ponemos en <code>true</code> y al revés. </p><p>Mostramos el signo <code>+</code> o <code>-</code> de acuerdo con el valor de <code>isActive</code> a través de un operador ternario.</p><p>Y si el valor del estado <code>isActive</code> es <code>true</code> entonces sólo vamos a mostrar el contenido del acordeón como en el ejemplo de abajo:</p><pre><code class="language-jsx">{isActive &amp;&amp; &lt;div className="accordion-content"&gt;{content}&lt;/div&gt;}
</code></pre><p>Si actualizas la aplicación, verás la siguiente pantalla:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://gist.github.com/myogeshchavan97/98ae4f4ead57fde8d47fcf7641220b72/raw/92ab4961521a20180285b6fdf5179b6d17fbcdff/open_close.gif" class="kg-image" alt="Open and close accordion" width="600" height="400" loading="lazy"></figure><p>Al hacer clic en el título, el acordeón se abre y podemos volver a hacer clic sobre él para cerrarlo.</p><h2 id="c-mo-a-adir-varias-secciones-a-un-men-acorde-n"><strong>Cómo añadir varias secciones a un menú acordeón</strong></h2><p>Si tenemos varias secciones, copiar y pegar una y otra vez el mismo código JSX no funcionará tan bien como si tenemos un solo una sección.</p><p>Entonces, lo que vamos a hacer es crear un componente separado solamente para mostrar el acordeón. Luego, de acuerdo con la cantidad de secciones que tengamos, vamos a usar un bucle para recorrer el componente y así hacer que muestre varias secciones.</p><p>Crea un nuevo archivo llamado <code>Accordion.js</code> dentro de la carpeta <code>src</code> y luego añade el siguiente código dentro de este:</p><pre><code class="language-jsx">import React, { useState } from 'react';

const Accordion = ({ title, content }) =&gt; {
  const [isActive, setIsActive] = useState(false);

  return (
    &lt;div className="accordion-item"&gt;
      &lt;div className="accordion-title" onClick={() =&gt; setIsActive(!isActive)}&gt;
        &lt;div&gt;{title}&lt;/div&gt;
        &lt;div&gt;{isActive ? '-' : '+'}&lt;/div&gt;
      &lt;/div&gt;
      {isActive &amp;&amp; &lt;div className="accordion-content"&gt;{content}&lt;/div&gt;}
    &lt;/div&gt;
  );
};

export default Accordion;
</code></pre><p>Aquí, lo que hemos hecho es mover el estado y el div <code>accordion-item</code> del archivo <code>App.js</code> al archivo <code>Accordion.js</code>. Luego, pasamos las propiedades dinámicas <code>title</code> y <code>content</code> usando la sintaxis de desestructuración de ES6: </p><pre><code class="language-js">const Accordion = ({ title, content }) =&gt; {
</code></pre><p>Ahora, abrimos el archivo <code>App.js</code> y reemplazamos el código con este:</p><pre><code class="language-jsx">import React from 'react';
import Accordion from './Accordion';

const App = () =&gt; {
  const accordionData = [
    {
      title: 'Section 1',
      content: `Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis sapiente
      laborum cupiditate possimus labore, hic temporibus velit dicta earum
      suscipit commodi eum enim atque at? Et perspiciatis dolore iure
      voluptatem.`
    },
    {
      title: 'Section 2',
      content: `Lorem ipsum, dolor sit amet consectetur adipisicing elit. Mollitia veniam
      reprehenderit nam assumenda voluptatem ut. Ipsum eius dicta, officiis
      quaerat iure quos dolorum accusantium ducimus in illum vero commodi
      pariatur? Impedit autem esse nostrum quasi, fugiat a aut error cumque
      quidem maiores doloremque est numquam praesentium eos voluptatem amet!
      Repudiandae, mollitia id reprehenderit a ab odit!`
    },
    {
      title: 'Section 3',
      content: `Sapiente expedita hic obcaecati, laboriosam similique omnis architecto ducimus magnam accusantium corrupti
      quam sint dolore pariatur perspiciatis, necessitatibus rem vel dignissimos
      dolor ut sequi minus iste? Quas?`
    }
  ];

  return (
    &lt;div&gt;
      &lt;h1&gt;React Accordion Demo&lt;/h1&gt;
      &lt;div className="accordion"&gt;
        {accordionData.map(({ title, content }) =&gt; (
          &lt;Accordion title={title} content={content} /&gt;
        ))}
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default App;
</code></pre><p>Lo que hicimos fue convertir el objeto <code>accordionData</code> en un arreglo de objetos. Luego, lo recorremos con un bucle usando el método <code>map</code>, además de pasarle las propiedades <code>title</code> y <code>content</code> al componente <code>Accordion</code>.</p><p>Si actualizas la aplicación, verás que se muestran las tres secciones y que podemos abrir y cerrar cada una de ellas:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://gist.github.com/myogeshchavan97/98ae4f4ead57fde8d47fcf7641220b72/raw/92ab4961521a20180285b6fdf5179b6d17fbcdff/final_working.gif" class="kg-image" alt="final working accordion" width="600" height="400" loading="lazy"></figure><h2 id="c-mo-refactorizar-el-c-digo"><strong>Cómo refactorizar el código</strong></h2><p>Como has visto, con solo mover la sección del acordeón a un componente separado y pasar el contenido dinámico mediante <em>props</em>, pudimos crear un acordeón desde cero y que funciona.</p><p>Como es una buena práctica dejar la información estática en un archivo separado, vamos a mover el <em>arreglo </em><code>accordionData</code> a un archivo distinto y luego importarlos desde <code>App.js</code></p><p>Crea un nuevo archivo llamado <code>content.js</code> dentro de la carpeta <code>utils</code> y copia el siguiente código dentro de este:</p><pre><code class="language-js">export const accordionData = [
  {
    title: 'Section 1',
    content: `Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis sapiente
    laborum cupiditate possimus labore, hic temporibus velit dicta earum
    suscipit commodi eum enim atque at? Et perspiciatis dolore iure
    voluptatem.`
  },
  {
    title: 'Section 2',
    content: `Lorem ipsum, dolor sit amet consectetur adipisicing elit. Mollitia veniam
    reprehenderit nam assumenda voluptatem ut. Ipsum eius dicta, officiis
    quaerat iure quos dolorum accusantium ducimus in illum vero commodi
    pariatur? Impedit autem esse nostrum quasi, fugiat a aut error cumque
    quidem maiores doloremque est numquam praesentium eos voluptatem amet!
    Repudiandae, mollitia id reprehenderit a ab odit!`
  },
  {
    title: 'Section 3',
    content: `Sapiente expedita hic obcaecati, laboriosam similique omnis architecto ducimus magnam accusantium corrupti
    quam sint dolore pariatur perspiciatis, necessitatibus rem vel dignissimos
    dolor ut sequi minus iste? Quas?`
  }
];
</code></pre><p>Ahora, abre <code>App.js</code> y reemplaza el código de ese archivo con este:</p><pre><code class="language-jsx">import React from 'react';
import Accordion from './Accordion';
import { accordionData } from './utils/content';

const App = () =&gt; {
  return (
    &lt;div&gt;
      &lt;h1&gt;React Accordion Demo&lt;/h1&gt;
      &lt;div className="accordion"&gt;
        {accordionData.map(({ title, content }) =&gt; (
          &lt;Accordion title={title} content={content} /&gt;
        ))}
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default App;
</code></pre><p>Con esto hemos importado la información estática desde un archivo externo y luego lo borramos del archivo <code>App.js</code>.</p><p>Entonces ahora el código se ve prolijo y fácil de leer, además de que continúa funcionando:</p><figure class="kg-card kg-image-card kg-width-wide"><img src="https://gist.github.com/myogeshchavan97/98ae4f4ead57fde8d47fcf7641220b72/raw/92ab4961521a20180285b6fdf5179b6d17fbcdff/final_working.gif" class="kg-image" alt="final working accordion" width="600" height="400" loading="lazy"></figure><h2 id="cierre"><strong>Cierre</strong></h2><p>Con esto, hemos terminado de hacer nuestra app.</p><p><strong>Puedes encontrar el código completo de esta aplicación en este <a href="https://github.com/myogeshchavan97/react-accordion-demo">repositorio de GitHub</a>.</strong></p><h3 id="-gracias-por-leer-"><strong>¡Gracias por leer!</strong></h3><p>Si quieres aprender en detalle todo lo nuevo de ES6+, incluyendo <em>let</em> y <em>const</em>, promesas, distintos métodos de promesas, desestructuración de objetos y <em>arreglos</em>, funciones flecha, <em>async/await</em>, importar y exportar, y mucho más desde cero, dale un vistazo a mi libro <a href="https://modernjavascript.yogeshchavan.dev/"><em>Mastering Modern JavaScript</em></a>. Este libro cubre todo lo necesario para aprender React y ayudarte a mejorar tanto en JavaScript como en React.</p><figure class="kg-card kg-image-card"><img src="https://gist.github.com/myogeshchavan97/98ae4f4ead57fde8d47fcf7641220b72/raw/92ab4961521a20180285b6fdf5179b6d17fbcdff/freecodecamp_image.jpeg" class="kg-image" alt="Mastering Modern JavaScript" width="600" height="400" loading="lazy"></figure> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Aprende React con APIs y Firebase - Curso desde cero ]]>
                </title>
                <description>
                    <![CDATA[ React es una de las bibliotecas de desarrollo web front-end más populares para crear aplicaciones web utilizando componentes reutilizables. Si eres desarrollador web, React puede ser muy útil para ti. Acabamos de publicar un curso de 5 horas en el canal de YouTube en español de freeCodeCamp.org [https://www.youtube.com/freecodecampespanol] que te ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/aprende-react-con-apis-y-firebase-curso-desde-cero/</link>
                <guid isPermaLink="false">6675cccfb6f94103d5aefaa5</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Estefania Cassingena Navone ]]>
                </dc:creator>
                <pubDate>Wed, 26 Jun 2024 04:00:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2024/06/Thumbnail-C.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>React es una de las bibliotecas de desarrollo web front-end más populares para crear aplicaciones web utilizando componentes reutilizables. Si eres desarrollador web, React puede ser muy útil para ti.</p><p>Acabamos de publicar un curso de 5 horas en el <a href="https://www.youtube.com/freecodecampespanol">canal de YouTube en español de freeCodeCamp.org</a> que te enseñará React paso a paso. Comenzarás instalando y configurando React con <code>create-react-app</code> y Vite y gradualmente aprenderás temas más avanzados, incluyendo componentes, JSX, enlaces, eventos, formularios y más.</p><p>Carpi Coder creó este curso. Carpi desarrollador web y ha aprendido de muchas comunidades, incluyendo freeCodeCamp. En 2020, creó su canal de YouTube para compartir sus conocimientos y ahora comparte su curso completo con la comunidad freeCodeCamp.</p><p>Antes de ver el contenido del curso en más detalle, veamos qué es React y por qué deberías aprenderlo...</p><h2 id="react-for-beginners"><strong>React for Beginners</strong></h2><p>React es una de las bibliotecas de desarrollo web front-end más populares en la comunidad de desarrollo web. Puedes verlo en los resultados de la <a href="https://survey.stackoverflow.co/2023/#web-frameworks-and-technologies" rel="noopener noreferrer nofollow">Stack Overflow Developer Survey 2023</a>.</p><p>Al responder a esta pregunta:</p><blockquote>¿Con qué frameworks y tecnologías web has realizado un trabajo de desarrollo extenso durante el año pasado y con cuáles deseas trabajar durante el próximo año?</blockquote><p>El <strong>40,58%</strong> de ellos escogió React.</p><p>React y Node.js fueron las tecnologías web más utilizadas por todos los encuestados.</p><figure class="kg-card kg-image-card"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719000934631/fb271486-f229-42a5-8629-746bb6310f0a.png" class="kg-image" alt="fb271486-f229-42a5-8629-746bb6310f0a" width="2940" height="1644" loading="lazy"></figure><p>Las ventajas clave de React incluyen:</p><ul><li><strong>Arquitectura basada en componentes</strong>. Las aplicaciones de React se crean dividiendo interfaces de usuario complejas en componentes más pequeños, simples e independientes.</li><li><strong>DOM virtual</strong>. React utiliza una representación virtual del DOM real de la aplicación web para realizar actualizaciones. Con este DOM virtual, React puede actualizar de manera eficiente solo las partes de la interfaz de usuario que deben actualizarse.</li><li><strong>Sintaxis JSX</strong>. Con JSX, puedes definir la estructura de los componentes de React con una sintaxis muy similar a HTML combinado con JavaScript.</li></ul><p>React también tiene una comunidad muy grande y activa de desarrolladores y bibliotecas para muchos propósitos diferentes. Es muy útil para crear aplicaciones web fáciles de mantener y fáciles de usar.</p><p><strong>💡 Dato: </strong>El logo oficial de React es un átomo. 👇</p><figure class="kg-card kg-image-card"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719002500087/60ae6dda-564c-4199-bd01-0f6560b4abd6.png" class="kg-image" alt="60ae6dda-564c-4199-bd01-0f6560b4abd6" width="2604" height="1498" loading="lazy"></figure><h2 id="curso-de-react-en-espa-ol"><strong>Curso de React en Español</strong></h2><p>Genial. Ahora que ya sabes más sobre React, veamos lo que aprenderás durante el curso:</p><ul><li>Cómo instalar React con <code>create-react-app</code> y con Vite.</li><li>JSX</li><li>Componentes</li><li>Props</li><li>Hooks</li><li>Ciclo de vida de los componentes</li><li>Promesas</li><li>Eventos</li><li>Formularios</li><li>Y más...</li></ul><p>💡<strong> Dato: </strong>este curso es ideal para principiantes. Si estás dando tus primeros pasos en React, Carpi Coder te guiará paso a paso.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719003104391/eab7016c-a491-4ba9-9020-23abe7e69d9a.png" class="kg-image" alt="eab7016c-a491-4ba9-9020-23abe7e69d9a" width="1920" height="1080" loading="lazy"><figcaption>Captura de pantalla del curso de React.</figcaption></figure><p>También aprenderás técnicas más avanzadas, como:</p><ul><li>Obtener y mostrar datos de una API.</li><li>Integrar su aplicación React con una base de datos de Firebase.</li><li>Hacer el deploy de tu aplicación React en Netlify, una plataforma en la nube que te permite implementar y administrar aplicaciones web y sitios web.</li></ul><p><strong>💡 Dato:</strong> Durante el curso, crearás la base de datos de Firebase paso a paso, así que puedes tomar el curso incluso si no tienes conocimientos previos de Firebase.</p><p>Te invitamos a ver el curso en el canal de YouTube de <a href="https://www.youtube.com/freecodecampespanol">freeCodeCamp.org en español</a>:</p><figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.49999999999999%;" class="fluid-width-video-wrapper">
            <iframe width="200" height="113" src="https://www.youtube.com/embed/oumXEuPM8RQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" title="Aprende React con APIs y Firebase - Curso desde cero" name="fitvid0"></iframe>
          </div>
        </div>
      </figure><p>✍️ Curso creado por Carpi Coder.</p><ul><li>YouTube: <a href="https://www.youtube.com/c/CarpiCoder" rel="noopener noreferrer nofollow">@carpicoder</a></li><li>Instagram: <a href="http://instagram.com/carpicoder" rel="noopener noreferrer nofollow">carpicoder</a></li><li>TikTok: <a href="http://tiktok.com/@carpicoder" rel="noopener noreferrer nofollow">@carpicoder</a></li><li>Sitio Web: <a href="https://carpicoder.com/" rel="noopener noreferrer nofollow">https://carpicoder.com/</a></li><li>X: <a href="https://x.com/carpicoder">@carpicoder</a></li><li>GitHub: <a href="https://github.com/carpicoder" rel="noopener noreferrer nofollow">carpicoder</a></li><li>LinkedIn: <a href="http://linkedin.com/in/matiascoletta" rel="noopener noreferrer nofollow">linkedin.com/in/matiascoletta</a></li></ul> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Formularios en ReactJS. Cómo manejarlos? ]]>
                </title>
                <description>
                    <![CDATA[ Cuando desarrollamos aplicaciones, una tarea común, fundamental y crítica es la recepción de datos por parte del usuario. Es esta actividad la inicia el proceso básico de un sistema:  Entrada (Input del usuario) --> Procesamiento --> Salida (Presentación de resultados). Veamos como realizamos esta tarea en ReactJS, partiendo de ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/formularios-en-react-como-manejarlos/</link>
                <guid isPermaLink="false">65a3c84351a59a0455c92a18</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Leonardo José Castillo Lacruz ]]>
                </dc:creator>
                <pubDate>Thu, 06 Jun 2024 14:03:32 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2024/06/Youtube--4-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Cuando desarrollamos aplicaciones, una tarea común, fundamental y crítica es la recepción de datos por parte del usuario. Es esta actividad la inicia el proceso básico de un sistema: </p><p><em>Entrada</em> (Input del usuario) --&gt; <em>Procesamiento</em> --&gt; <em>Salida</em> (Presentación de resultados).</p><p>Veamos como realizamos esta tarea en ReactJS, partiendo de la forma básica, para ello vamos a crear un componente llamado <code>Formulario</code> el cual va a permitirle al usuario indicar sus datos y luego estos serán enviados a una API para ser procesados y guardados.</p><p>Recordemos que con ReactJS implementamos SPAs (<em>Single Page Applications</em>), por lo tanto, la gestión del formulario va a variar un poco con relación a un <em>form</em> tradicional de HTML.</p><h2 id="formulario-como-componente-con-usestate">Formulario como componente con useState</h2><p>Para este componente formulario, nos vamos a apoyar en el hook <code>useState</code>, de forma tal que podamos capturar los valores introducidos por el usuario, toda vez que haga algún cambio en él alguna de las entradas disponibles. El código de ese componente será el siguiente:</p><pre><code>import React, { useState } from 'react';

const countries = [
  "Argentina", "Bolivia", "Brasil", "Chile", "Colombia",
  "Ecuador", "Guyana", "Paraguay", "Perú", "Surinam",
  "Uruguay", "Venezuela"
];

const Formulario = () =&gt; {
  const [formData, setFormData] = useState({
    email: '',
    password: '',
    confirmPassword: '',
    name: '',
    country: '',
    phone: '',
    photo: null
  });

  const handleChange = (e) =&gt; {
    const { name, value } = e.target;
    setFormData({ ...formData, [name]: value });
  };

  const handlePhotoChange = (e) =&gt; {
    setFormData({ ...formData, photo: e.target.files[0] });
  };

  const handleSubmit = (e) =&gt; {
    e.preventDefault();
    console.log('Form data submitted:', formData);
  };

  return (
    &lt;div className="App"&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;h2&gt;Datos de Acceso&lt;/h2&gt;
        &lt;div&gt;
          &lt;label&gt;Email:&lt;/label&gt;
          &lt;input type="email" name="email" value={formData.email} onChange={handleChange} required /&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;label&gt;Clave:&lt;/label&gt;
          &lt;input type="password" name="password" value={formData.password} onChange={handleChange} required /&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;label&gt;Confirmar Clave:&lt;/label&gt;
          &lt;input type="password" name="confirmPassword" value={formData.confirmPassword} onChange={handleChange} required /&gt;
        &lt;/div&gt;
        &lt;h2&gt;Datos Personales&lt;/h2&gt;
        &lt;div&gt;
          &lt;label&gt;Nombre:&lt;/label&gt;
          &lt;input type="text" name="name" value={formData.name} onChange={handleChange} required /&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;label&gt;País:&lt;/label&gt;
          &lt;select name="country" value={formData.country} onChange={handleChange} required&gt;
            &lt;option value=""&gt;Seleccione un país&lt;/option&gt;
            {countries.map(country =&gt; (
              &lt;option key={country} value={country}&gt;{country}&lt;/option&gt;
            ))}
          &lt;/select&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;label&gt;Teléfono:&lt;/label&gt;
          &lt;input type="tel" name="phone" value={formData.phone} onChange={handleChange} required /&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;label&gt;Foto:&lt;/label&gt;
          &lt;input type="file" name="photo" accept="image/*" onChange={handlePhotoChange} required /&gt;
        &lt;/div&gt;
        &lt;button type="submit"&gt;Enviar&lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
};

export default Formulario;</code></pre><p>El componente tiene 3 funciones, las cuales explicamos:</p><ul><li><code>handleChange</code>, encargada de capturar el evento <code>onChange</code> de las cajas y listas del formulario, con ello actualizamos el estado definido al inicio que contendrá los valores indicados por el usuario.</li><li><code>handlePhotoChange</code>, la cual nos ayudará a capturar la información de la foto solicitada en el formulario, siendo que el tipo de input <code>file</code> se maneja de forma diferente, por ello es importante separar esta funcionalidad.</li><li><code>handleSubmit</code>, en esta función implementaremos lo necesario para enviar los datos a la capa de backend.</li></ul><h3 id="enviando-la-foto-en-formato-base64">Enviando la foto en formato base64</h3><p>Es común que el envío de archivos implique realizar algunos procesos adicionales, por ejemplo, tomar el contenido y codificarlo en <code>base64</code> para transformarlo en un string y así poderlo enviar de forma más fácil a la capa de backend, para ello agregamos una función responsable de esa tarea en el código:</p><pre><code>const toBase64 = (file) =&gt; new Promise((resolve, reject) =&gt; {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () =&gt; resolve(reader.result);
    reader.onerror = (error) =&gt; reject(error);
  });</code></pre><p>Observa, que por ser una operación que implica tiempo no determinado, JavaScript la maneja como una promesa, es decir, cuando finalice la conversión, nos va a llamar y nos dará el resultado.</p><p>Además de hacer esa transformación, es importante que enviemos los datos de nombre original de archivo, su tamaño y su tipo de contenido, ya que en la capa de backend esos datos serán necesarios, para ello mejoramos la función <code>handlePhotoChange</code>, dejándola de esta forma:</p><pre><code>  const handlePhotoChange = (e) =&gt; {
    const file = e.target.files[0];
    if (file) {
      setFormData({
        ...formData,
        photo: file,
        photoName: file.name,
        photoType: file.type,
        photoSize: file.size
      });
    }
  };</code></pre><p>Ahora con este proceso podemos mejorar nuestra función <code>handleSubmit</code> incorporando la conversión de la foto y el envío de los datos a la capa de backend.</p><pre><code>const handleSubmit = async (e) =&gt; {
    e.preventDefault();

    // Convertir la foto a base64
    let base64Photo = '';
    if (formData.photo) {
      base64Photo = await toBase64(formData.photo);
    }

    // Crear el objeto con los datos del formulario
    const dataToSend = {
      email: formData.email,
      password: formData.password,
      confirmPassword: formData.confirmPassword,
      name: formData.name,
      country: formData.country,
      phone: formData.phone,
      photo: {
        base64: base64Photo,
        name: formData.photoName,
        type: formData.photoType,
        size: formData.photoSize
      }
    };

    // Enviar la solicitud POST a la API
    try {
      const response = await fetch('https://leonardojose.dev/api/usuarios', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(dataToSend)
      });

      if (!response.ok) {
        throw new Error('Error en la solicitud');
      }

      const result = await response.json();
      console.log('Form data submitted:', result);
    } catch (error) {
      console.error('Error:', error);
    }
  };</code></pre><p>Recuerda, React trabaja como SPA, por lo tanto, el envío de datos es asíncrono, es por ello que lo primero que realizamos en la función de submit es evitar que el navegador nos haga un envío de datos o POST con la línea: <code>e.preventDefault();</code> </p><h2 id="trabajando-con-typescript">Trabajando con TypeScript</h2><p>El código anteriormente implementado es totalmente váildo cuando trabajamos en JavaScript, solo que actualmente, ha tenido un gran auge, el uso del <em>SuperJavascript</em>, o mejor conocido como <strong>TypeScript</strong>. </p><p>Vamos a convertir nuestro componente a lenguaje typescript, para ello vamos a definir los tipos de datos correctamente, vamos a cambiar la extensión del componente para <code>Formulario.tsx</code> y con ello vamos a aprovechar las bondades que TypeScript, nos da, la principal para mí, poder determinar errores en tiempo de compilación.</p><p>El código del componente ahora quedará:</p><pre><code>import React, { useState, ChangeEvent, FormEvent } from 'react';

const countries = [
  "Argentina", "Bolivia", "Brasil", "Chile", "Colombia",
  "Ecuador", "Guyana", "Paraguay", "Perú", "Surinam",
  "Uruguay", "Venezuela"
];

interface FormData {
  email: string;
  password: string;
  confirmPassword: string;
  name: string;
  country: string;
  phone: string;
  photo: File | null;
  photoName: string;
  photoType: string;
  photoSize: number | string;
}

const Formulario: React.FC = () =&gt; {
  const [formData, setFormData] = useState&lt;FormData&gt;({
    email: '',
    password: '',
    confirmPassword: '',
    name: '',
    country: '',
    phone: '',
    photo: null,
    photoName: '',
    photoType: '',
    photoSize: ''
  });

  const handleChange = (e: ChangeEvent&lt;HTMLInputElement | HTMLSelectElement&gt;) =&gt; {
    const { name, value } = e.target;
    setFormData({ ...formData, [name]: value });
  };

  const handlePhotoChange = (e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    const file = e.target.files ? e.target.files[0] : null;
    if (file) {
      setFormData({
        ...formData,
        photo: file,
        photoName: file.name,
        photoType: file.type,
        photoSize: file.size
      });
    }
  };

  const handleSubmit = async (e: FormEvent) =&gt; {
    e.preventDefault();

    // Convertir la foto a base64
    let base64Photo = '';
    if (formData.photo) {
      base64Photo = await toBase64(formData.photo);
    }

    // Crear el objeto con los datos del formulario
    const dataToSend = {
      email: formData.email,
      password: formData.password,
      confirmPassword: formData.confirmPassword,
      name: formData.name,
      country: formData.country,
      phone: formData.phone,
      photo: {
        base64: base64Photo,
        name: formData.photoName,
        type: formData.photoType,
        size: formData.photoSize
      }
    };

    // Enviar la solicitud POST a la API
    try {
      const response = await fetch('https://leonardojose.dev/api/usuarios', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(dataToSend)
      });

      if (!response.ok) {
        throw new Error('Error en la solicitud');
      }

      const result = await response.json();
      console.log('Form data submitted:', result);
    } catch (error) {
      console.error('Error:', error);
    }
  };

  const toBase64 = (file: File) =&gt; new Promise&lt;string&gt;((resolve, reject) =&gt; {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () =&gt; resolve(reader.result as string);
    reader.onerror = (error) =&gt; reject(error);
  });

  return (
    &lt;div className="App"&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;h2&gt;Datos de Acceso&lt;/h2&gt;
        &lt;div&gt;
          &lt;label&gt;Email:&lt;/label&gt;
          &lt;input type="email" name="email" value={formData.email} onChange={handleChange} required /&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;label&gt;Clave:&lt;/label&gt;
          &lt;input type="password" name="password" value={formData.password} onChange={handleChange} required /&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;label&gt;Confirmar Clave:&lt;/label&gt;
          &lt;input type="password" name="confirmPassword" value={formData.confirmPassword} onChange={handleChange} required /&gt;
        &lt;/div&gt;
        &lt;h2&gt;Datos Personales&lt;/h2&gt;
        &lt;div&gt;
          &lt;label&gt;Nombre:&lt;/label&gt;
          &lt;input type="text" name="name" value={formData.name} onChange={handleChange} required /&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;label&gt;País:&lt;/label&gt;
          &lt;select name="country" value={formData.country} onChange={handleChange} required&gt;
            &lt;option value=""&gt;Seleccione un país&lt;/option&gt;
            {countries.map(country =&gt; (
              &lt;option key={country} value={country}&gt;{country}&lt;/option&gt;
            ))}
          &lt;/select&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;label&gt;Teléfono:&lt;/label&gt;
          &lt;input type="tel" name="phone" value={formData.phone} onChange={handleChange} required /&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;label&gt;Foto:&lt;/label&gt;
          &lt;input type="file" name="photo" accept="image/*" onChange={handlePhotoChange} required /&gt;
        &lt;/div&gt;
        &lt;button type="submit"&gt;Enviar&lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
};

export default Formulario;</code></pre><p>Observa que aparecen los tipos de datos de cada propiedad, de casda evento e incluso del componente React, algo que es fundamental para la mantenibilidad y legibilidad del código.</p><p>Luego nuestro código podrá ser incoporado en proyectos que usen TypeScript como base, lo cual está sucediendo con más frecuencia cada día.</p><h2 id="mejorando-la-visual-del-formulario">Mejorando la visual del formulario</h2><p>Hasta ahora nuestro código apenas tiene HTML y no usa nada de las bondades de CSS, por lo tanto, será funcional, pero no será agradable a la vista. Para ello vamos a incorporarle estilos del framework para CSS <code>tailwind</code>, específicamente del plugin <code>daisyui</code>.</p><p>Para instalar Tailwind en una aplicación <code>react</code> con <code>vite</code>, puedes seguir estas instrucciones:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.freecodecamp.org/news/how-to-install-tailwindcss-in-react/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">How to Setup React and Tailwind CSS with Vite in a Project</div><div class="kg-bookmark-description">Tailwind CSS is a popular CSS framework, and React is one of the most popularJavaScript libraries. And Tailwind CSS and React are a great combo to use if you’re building afrontend project. In this article, you will learn how to setup your coding environment with Vite,install React and Tailwind …</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://cdn.freecodecamp.org/universal/favicons/favicon.ico" width="48" height="48" alt="favicon" loading="lazy"><span class="kg-bookmark-author">freeCodeCamp.org</span><span class="kg-bookmark-publisher">Segun Ajibola</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.freecodecamp.org/news/content/images/2023/01/Parameters-vs-Arguments--1-.png" width="600" height="400" alt="Parameters-vs-Arguments--1-" loading="lazy"></div></a></figure><p>Luego de instalado Tailwind, debemos instalar daisyui, esto lo logramos con las siguientes instrucciones:</p><pre><code>npm install daisyui</code></pre><p>Incluso es posible usar un paquete que ya genera los componentes para react, llamado react-daisyui (no lo usamos aquí, pero es interesante conocerlo), el cual podemos instalar con el siguiente comando:</p><pre><code>npm install react-daisyui</code></pre><p>Ahora corresponde realizar el ajuste en nuestro componente, de forma que podamos aprovechar la visual de <code>daisyui</code>, quedando nuestro componente como sigue:</p><pre><code>import React, { useState, ChangeEvent, FormEvent } from 'react';
import 'daisyui/dist/full.css';

const countries = [
  "Argentina", "Bolivia", "Brasil", "Chile", "Colombia",
  "Ecuador", "Guyana", "Paraguay", "Perú", "Surinam",
  "Uruguay", "Venezuela"
];

interface FormData {
  email: string;
  password: string;
  confirmPassword: string;
  name: string;
  country: string;
  phone: string;
  photo: File 
  photoName: string;
  photoType: string;
  photoSize: number | string;
}

const Formulario: React.FC = () =&gt; {
  const [formData, setFormData] = useState&lt;FormData&gt;({
    email: '',
    password: '',
    confirmPassword: '',
    name: '',
    country: '',
    phone: '',
    photo: null,
    photoName: '',
    photoType: '',
    photoSize: ''
  });

  const handleChange = (e: ChangeEvent&lt;HTMLInputElement | HTMLSelectElement&gt;) =&gt; {
    const { name, value } = e.target;
    setFormData({ ...formData, [name]: value });
  };

  const handlePhotoChange = (e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    const file = e.target.files ? e.target.files[0] : null;
    if (file) {
      setFormData({
        ...formData,
        photo: file,
        photoName: file.name,
        photoType: file.type,
        photoSize: file.size
      });
    }
  };

  const handleSubmit = async (e: FormEvent) =&gt; {
    e.preventDefault();

    // Convertir la foto a base64
    let base64Photo = '';
    if (formData.photo) {
      base64Photo = await toBase64(formData.photo);
    }

    // Crear el objeto con los datos del formulario
    const dataToSend = {
      email: formData.email,
      password: formData.password,
      confirmPassword: formData.confirmPassword,
      name: formData.name,
      country: formData.country,
      phone: formData.phone,
      photo: {
        base64: base64Photo,
        name: formData.photoName,
        type: formData.photoType,
        size: formData.photoSize
      }
    };

    // Enviar la solicitud POST a la API
    try {
      const response = await fetch('https://leonardojose.dev/api/usuarios', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(dataToSend)
      });

      if (!response.ok) {
        throw new Error('Error en la solicitud');
      }

      const result = await response.json();
      console.log('Form data submitted:', result);
    } catch (error) {
      console.error('Error:', error);
    }
  };

  const toBase64 = (file: File) =&gt; new Promise&lt;string&gt;((resolve, reject) =&gt; {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () =&gt; resolve(reader.result as string);
    reader.onerror = (error) =&gt; reject(error);
  });

  return (
    &lt;div className="App container mx-auto p-4"&gt;
      &lt;form onSubmit={handleSubmit} className="bg-white p-6 rounded shadow-md w-full max-w-lg mx-auto"&gt;
        &lt;h2 className="text-2xl font-bold mb-4"&gt;Datos de Acceso&lt;/h2&gt;
        &lt;div className="mb-4"&gt;
          &lt;label className="block text-sm font-medium mb-1"&gt;Email:&lt;/label&gt;
          &lt;input
            type="email"
            name="email"
            value={formData.email}
            onChange={handleChange}
            required
            className="input input-bordered w-full"
          /&gt;
        &lt;/div&gt;
        &lt;div className="mb-4"&gt;
          &lt;label className="block text-sm font-medium mb-1"&gt;Clave:&lt;/label&gt;
          &lt;input
            type="password"
            name="password"
            value={formData.password}
            onChange={handleChange}
            required
            className="input input-bordered w-full"
          /&gt;
        &lt;/div&gt;
        &lt;div className="mb-4"&gt;
          &lt;label className="block text-sm font-medium mb-1"&gt;Confirmar Clave:&lt;/label&gt;
          &lt;input
            type="password"
            name="confirmPassword"
            value={formData.confirmPassword}
            onChange={handleChange}
            required
            className="input input-bordered w-full"
          /&gt;
        &lt;/div&gt;
        &lt;h2 className="text-2xl font-bold mb-4"&gt;Datos Personales&lt;/h2&gt;
        &lt;div className="mb-4"&gt;
          &lt;label className="block text-sm font-medium mb-1"&gt;Nombre:&lt;/label&gt;
          &lt;input
            type="text"
            name="name"
            value={formData.name}
            onChange={handleChange}
            required
            className="input input-bordered w-full"
          /&gt;
        &lt;/div&gt;
        &lt;div className="mb-4"&gt;
          &lt;label className="block text-sm font-medium mb-1"&gt;País:&lt;/label&gt;
          &lt;select
            name="country"
            value={formData.country}
            onChange={handleChange}
            required
            className="select select-bordered w-full"
          &gt;
            &lt;option value=""&gt;Seleccione un país&lt;/option&gt;
            {countries.map(country =&gt; (
              &lt;option key={country} value={country}&gt;{country}&lt;/option&gt;
            ))}
          &lt;/select&gt;
        &lt;/div&gt;
        &lt;div className="mb-4"&gt;
          &lt;label className="block text-sm font-medium mb-1"&gt;Teléfono:&lt;/label&gt;
          &lt;input
            type="tel"
            name="phone"
            value={formData.phone}
            onChange={handleChange}
            required
            className="input input-bordered w-full"
          /&gt;
        &lt;/div&gt;
        &lt;div className="mb-4"&gt;
          &lt;label className="block text-sm font-medium mb-1"&gt;Foto:&lt;/label&gt;
          &lt;input
            type="file"
            name="photo"
            accept="image/*"
            onChange={handlePhotoChange}
            required
            className="file-input file-input-bordered w-full"
          /&gt;
        &lt;/div&gt;
        &lt;button type="submit" className="btn btn-primary w-full"&gt;Enviar&lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
};

export default Formulario;</code></pre><p>Observa que el código anterior ahora:</p><ol><li><strong>Importa DaisyUI CSS:</strong> <strong><code>import 'daisyui/dist/full.css';</code></strong> asegurando que DaisyUI está incluido.</li><li><strong>Uso de Clases de DaisyUI:</strong> Se aplicaron clases de DaisyUI como <strong><code>input input-bordered</code></strong>, <strong><code>select select-bordered</code></strong>, y <strong><code>btn btn-primary</code></strong> para estilizar los componentes del formulario.</li><li><strong>Estructura del Formulario:</strong> La estructura del formulario incluye contenedores como <strong><code>div</code></strong> y <strong><code>form</code></strong> con clases de TailwindCSS y DaisyUI para un diseño limpio y responsive.</li></ol><p>Con ello logramos una apariencia mucho más linda y agradable. </p><p>Puedes observar en el siguiente video, lo que hemos realizado y lo que explicaremos más adelante, con lo cual vas a reforzar los conocimientos.</p><figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.49999999999999%;" class="fluid-width-video-wrapper">
            <iframe width="200" height="113" src="https://www.youtube.com/embed/b6gUczGRcgM?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" title="Formularios en ReactJS  -  Conociendo a react hook form" name="fitvid0"></iframe>
          </div>
        </div>
      </figure><h2 id="incorporando-a-react-hook-form">Incorporando a react-hook-form</h2><p>En el código anterior, la gestión de los valores la hicimos, usando a <code>useState</code>, no tenemos gestión de errores y tampoco tenemos validaciones. Podemos realizar esto de forma manual, sí, pero si nos aprovechamos de un paquete que ya realiza esto, y que además es altamente usado por la comunidad, es decir, está probado y es código que los desarrolladores ya conocemos, ¿mejor aún cierto?</p><p>Para instalar a react-hook-form, solo debemos hacer lo siguiente:</p><pre><code>npm install react-hook-form</code></pre><p>Luego incorporamos los hooks del paquete de esta forma:</p><pre><code>const { register, handleSubmit, watch, formState: { errors } } = useForm&lt;FormData&gt;();
const photoFile = watch('photo');</code></pre><ul><li><code>register</code>, nos permite indicarle al hook qué campos del formulario vamos a utilizar.</li><li><code>handleSubmit</code>, es la función propia de react-hook-form que va a manejar los valores de los campos (esto antes lo haciamos nosotros manualmente), y luego podemos indicar un callback para llamar nuestro backend.</li><li><code>watch</code>, nos permite colocar una referencia para saber en todo momento el valor de un campo, en nuestro caso observamos la foto porque debemos convertirla a base64.</li><li><code>formState</code>, nos indica en que estado está el formulario, por enviar, enviando o enviado y nos permite obtener los errores que se produjeron al momento de validar los campos (sí! Validaciones 😃)</li></ul><p>Y que tal agregar algunas validaciones, como por ejemplo que la contraseña sea a la confirmación, y que la contraseña además tenga 8 caracteres, un número y un carácter especial. Queremos adicionalmente que la foto venga en formato png o jpg. Todo esto es posible haciendo uso de <code>react-hook-form</code>.</p><p>Veamos algunos de los ajustes que debemos realizar para que nuestro formulario sea fácilmente administrable con <code>react-hook-form</code>:</p><pre><code>&lt;input
            type="email"
            {...register('email', { required: "Email es requerido" })}
            className="input input-bordered w-full"
          /&gt;
          {errors.email &amp;&amp; &lt;span className="text-red-500 text-sm"&gt;{errors.email.message}&lt;/span&gt;}</code></pre><p>Observa que agregamos el uso de <code>register</code> y colocamos una validación, que sea obligatorio el valor, luego si hay error lo mostramos en pantalla.</p><p>Ahora veamos un caso más complejo, la contraseña:</p><pre><code>&lt;input
            type="password"
            {...register('password', {
              required: "Clave es requerida",
              minLength: { value: 8, message: "La clave debe tener al menos 8 caracteres" },
              pattern: {
                value: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&amp;])[A-Za-z\d@$!%*#?&amp;]{8,}$/,
                message: "La clave debe contener al menos un número y un caracter especial"
              }
            })}
            className="input input-bordered w-full"
          /&gt;
          {errors.password &amp;&amp; &lt;span className="text-red-500 text-sm"&gt;{errors.password.message}&lt;/span&gt;}</code></pre><p>Se puede identificar que cuando usamos el <code>register</code>, podemos indicar si es obligatorio (<code>required</code>), el mensaje de error (<code>message</code>) y el tamaño del valor mínimo aceptable (<code>minLength</code>) y el patrón (<code>pattern</code>) del valor para obligar colocar un número y un carácter especial. </p><p>Con solo eso ya hemos hecho validaciones complejas de ese campo.</p><p>Veamos como queda el componente luego de todos los cambios realizados:</p><pre><code>import React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
import 'daisyui/dist/full.css';

const countries = [
  "Argentina", "Bolivia", "Brasil", "Chile", "Colombia",
  "Ecuador", "Guyana", "Paraguay", "Perú", "Surinam",
  "Uruguay", "Venezuela"
];

interface FormData {
  email: string;
  password: string;
  confirmPassword: string;
  name: string;
  country: string;
  phone: string;
  photo: FileList;
}

const Formulario: React.FC = () =&gt; {
  const { register, handleSubmit, watch, formState: { errors } } = useForm&lt;FormData&gt;();
  const password = watch('password');
  const photoFile = watch('photo');

  const onSubmit: SubmitHandler&lt;FormData&gt; = async (data) =&gt; {
    // Convertir la foto a base64
    let base64Photo = '';
    const file = data.photo[0];
    if (file) {
      base64Photo = await toBase64(file);
    }

    // Crear el objeto con los datos del formulario
    const dataToSend = {
      email: data.email,
      password: data.password,
      confirmPassword: data.confirmPassword,
      name: data.name,
      country: data.country,
      phone: data.phone,
      photo: {
        base64: base64Photo,
        name: file.name,
        type: file.type,
        size: file.size
      }
    };

    // Enviar la solicitud POST a la API
    try {
      const response = await fetch('https://leonardojose.dev/api/usuarios', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(dataToSend)
      });

      if (!response.ok) {
        throw new Error('Error en la solicitud');
      }

      const result = await response.json();
      console.log('Form data submitted:', result);
    } catch (error) {
      console.error('Error:', error);
    }
  };

  const toBase64 = (file: File) =&gt; new Promise&lt;string&gt;((resolve, reject) =&gt; {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () =&gt; resolve(reader.result as string);
    reader.onerror = (error) =&gt; reject(error);
  });

  return (
    &lt;div className="App container mx-auto p-4"&gt;
      &lt;form onSubmit={handleSubmit(onSubmit)} className="bg-white p-6 rounded shadow-md w-full max-w-lg mx-auto"&gt;
        &lt;h2 className="text-2xl font-bold mb-4"&gt;Datos de Acceso&lt;/h2&gt;
        &lt;div className="mb-4"&gt;
          &lt;label className="block text-sm font-medium mb-1"&gt;Email:&lt;/label&gt;
          &lt;input
            type="email"
            {...register('email', { required: "Email es requerido" })}
            className="input input-bordered w-full"
          /&gt;
          {errors.email &amp;&amp; &lt;span className="text-red-500 text-sm"&gt;{errors.email.message}&lt;/span&gt;}
        &lt;/div&gt;
        &lt;div className="mb-4"&gt;
          &lt;label className="block text-sm font-medium mb-1"&gt;Clave:&lt;/label&gt;
          &lt;input
            type="password"
            {...register('password', {
              required: "Clave es requerida",
              minLength: { value: 8, message: "La clave debe tener al menos 8 caracteres" },
              pattern: {
                value: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&amp;])[A-Za-z\d@$!%*#?&amp;]{8,}$/,
                message: "La clave debe contener al menos un número y un caracter especial"
              }
            })}
            className="input input-bordered w-full"
          /&gt;
          {errors.password &amp;&amp; &lt;span className="text-red-500 text-sm"&gt;{errors.password.message}&lt;/span&gt;}
        &lt;/div&gt;
        &lt;div className="mb-4"&gt;
          &lt;label className="block text-sm font-medium mb-1"&gt;Confirmar Clave:&lt;/label&gt;
          &lt;input
            type="password"
            {...register('confirmPassword', {
              required: "Confirmar clave es requerido",
              validate: value =&gt; value === password || "Las claves no coinciden"
            })}
            className="input input-bordered w-full"
          /&gt;
          {errors.confirmPassword &amp;&amp; &lt;span className="text-red-500 text-sm"&gt;{errors.confirmPassword.message}&lt;/span&gt;}
        &lt;/div&gt;
        &lt;h2 className="text-2xl font-bold mb-4"&gt;Datos Personales&lt;/h2&gt;
        &lt;div className="mb-4"&gt;
          &lt;label className="block text-sm font-medium mb-1"&gt;Nombre:&lt;/label&gt;
          &lt;input
            type="text"
            {...register('name', { required: "Nombre es requerido" })}
            className="input input-bordered w-full"
          /&gt;
          {errors.name &amp;&amp; &lt;span className="text-red-500 text-sm"&gt;{errors.name.message}&lt;/span&gt;}
        &lt;/div&gt;
        &lt;div className="mb-4"&gt;
          &lt;label className="block text-sm font-medium mb-1"&gt;País:&lt;/label&gt;
          &lt;select
            {...register('country', { required: "País es requerido" })}
            className="select select-bordered w-full"
          &gt;
            &lt;option value=""&gt;Seleccione un país&lt;/option&gt;
            {countries.map(country =&gt; (
              &lt;option key={country} value={country}&gt;{country}&lt;/option&gt;
            ))}
          &lt;/select&gt;
          {errors.country &amp;&amp; &lt;span className="text-red-500 text-sm"&gt;{errors.country.message}&lt;/span&gt;}
        &lt;/div&gt;
        &lt;div className="mb-4"&gt;
          &lt;label className="block text-sm font-medium mb-1"&gt;Teléfono:&lt;/label&gt;
          &lt;input
            type="tel"
            {...register('phone', {
              required: "Teléfono es requerido",
              pattern: {
                value: /^[0-9()-]+$/,
                message: "Solo se permiten números, paréntesis y guiones"
              }
            })}
            className="input input-bordered w-full"
          /&gt;
          {errors.phone &amp;&amp; &lt;span className="text-red-500 text-sm"&gt;{errors.phone.message}&lt;/span&gt;}
        &lt;/div&gt;
        &lt;div className="mb-4"&gt;
          &lt;label className="block text-sm font-medium mb-1"&gt;Foto:&lt;/label&gt;
          &lt;input
            type="file"
            {...register('photo', {
              required: "Foto es requerida",
              validate: {
                acceptedFormats: files =&gt; files[0] &amp;&amp; ['image/jpeg', 'image/png'].includes(files[0]?.type) || "Solo se permiten fotos en formato PNG o JPG"
              }
            })}
            accept="image/*"
            className="file-input file-input-bordered w-full"
          /&gt;
          {errors.photo &amp;&amp; &lt;span className="text-red-500 text-sm"&gt;{errors.photo.message}&lt;/span&gt;}
        &lt;/div&gt;
        &lt;button type="submit" className="btn btn-primary w-full"&gt;Enviar&lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
};

export default Formulario;</code></pre><p>En el código anterior hemos realizado lo siguiente:</p><ol><li><strong>Validación del Email:</strong> <strong><code>required: "Email es requerido"</code></strong>.</li><li><strong>Validación de la Clave:</strong></li></ol><ul><li>Mínimo 8 caracteres.</li><li>Al menos un número y un carácter especial.</li></ul><p>3. <strong>Validación de Confirmar Clave:</strong> Verificamos que coincida con la clave.</p><p>4. <strong>Validación del País:</strong> Obligatorio.</p><p>5.<strong> Validación del Teléfono:</strong> Obligatorio y solo números</p><p>6. <strong>Validación de la Foto:</strong></p><ul><li>Obligatorio.</li><li>Formatos permitidos: PNG o JPG.</li></ul><p>En el video indicado anteriormente vas a poder visualizar en detalle cada paso realizado y podrás ver como evolucionamos en el uso de formulario en la aplicación que tomamos de ejemplo.</p><p>Para cualquier duda o comentario, me puedes enviar un mensaje en mis redes:</p><ul><li>YouTube: <a href="https://www.youtube.com/leonardocastillo79" rel="noopener noreferrer nofollow">@LeonardoCastillo79</a></li><li>LinkedIn: <a href="https://www.linkedin.com/in/leonardo-castillo-4911571a/" rel="noopener noreferrer nofollow">Leonardo José Castillo Lacruz</a></li><li>Twitter: <a href="https://twitter.com/ljcl79" rel="noopener noreferrer nofollow">@ljcl79</a></li><li>GitHub: <a href="https://github.com/ljcl79" rel="noopener noreferrer nofollow">@ljcl79</a></li></ul> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Caché en React: Cómo usar los hooks useMemo y useCallback ]]>
                </title>
                <description>
                    <![CDATA[ A medida que te vuelves más proficiente programando en React, el rendimiento se convertirá en un punto focal importante en tu proceso de desarrollo. Como con cualquier herramienta o metodología de programación, el caché juega un rol enorme cuando se trata de optimizar aplicaciones de React. Caché en React típicamente ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/cache-en-react-como-usar-los-hooks-usememo-y-usecallback/</link>
                <guid isPermaLink="false">660b51ba6e403a04017cd106</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Elias Ezequiel Pereyra Gomez ]]>
                </dc:creator>
                <pubDate>Tue, 23 Apr 2024 01:22:06 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2024/04/caching-react.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Artículo original:</strong> <a href="https://www.freecodecamp.org/news/caching-in-react/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Caching in React – How to Use the useMemo and useCallback Hooks</a>
      </p><p>A medida que te vuelves más proficiente programando en React, el rendimiento se convertirá en un punto focal importante en tu proceso de desarrollo.</p><p>Como con cualquier herramienta o metodología de programación, el caché juega un rol enorme cuando se trata de optimizar aplicaciones de React.</p><p>Caché en React típicamente va por el término de <em><a href="https://es.wikipedia.org/wiki/Memoizaci%C3%B3n">memoización</a></em>. Se usa para mejorar el rendimiento al reducir la cantidad de veces que un componente se renderiza debido al estado o a las mutaciones del prop.</p><p>React provee dos APIs para la caché: useMemo y useCallback. useCallback es un hook que memoiza una función, mientras que useMemo es un hook que memoiza un valor. Estos dos hooks son frecuentemente usados en conjunción con la API Context para mejorar aún más la eficiencia.</p><p>Aquí hay una lista básica de tópicos que estaremos cubriendo en este artículo:</p><ol><li>Comportamiento predeterminado de almacenamiento en caché en React.</li><li>El hook useMemo.</li><li>El hook useCallback.</li></ol><p>Para seguir adelante, necesitarás un entendimiento bastante bueno de React y de componentes con estado.</p><h2 id="comportamiento-predeterminado-de-almacenamiento-en-cach-en-react"><strong>Comportamiento predeterminado de </strong>almacenamiento en <strong>caché en React</strong></h2><p>Por defecto, React usa una técnica llamada "comparación superficial" para determinar si un componente debería ser re-renderizado. Esto significa básicamente que si las propiedades o estado de un componente no han cambiado, React asumirá que la salida del componente tampoco ha cambiado y no lo re-renderizará.</p><p>Mientras que este comportamiento de almacenamiento en caché predeterminado es muy efectivo por sí mismo, no siempre es suficiente para optimizar componentes complejos que requieren una gestión de estado avanzado.</p><p>Para lograr más control sobre la caché de tus componentes y el comportamiento de la renderización, React ofrece los hooks <strong>useMemo</strong> y <strong>useCallback</strong>.</p><h2 id="cach-en-react-con-el-hook-usememo"><strong>Caché en React con el hook useMemo</strong></h2><p>useMemo es útil cuando necesitas hacer un cálculo costoso para devolver un valor, y quieres asegurarte que el cálculo solamente se ejecute cuando sea necesario. Al memoizar el valor usando useMemo, puedes asegurarte que el valor se ejecute solamente cuando sus dependencias cambien.</p><p>En un componente de React, puedes tener múltiples propiedades que forman tu estado. Si una pieza del estado cambia, el cual no tiene nada que ver con nuestro valor costoso, ¿por qué volver a ejecutarlo si este no ha cambiado?</p><p>Aquí hay un bloque de código de ejemplo que refleja una implementación básica de useMemo: &nbsp;</p><pre><code class="language-jsx">import React, { useState, useMemo } from 'react';

function Example() {
	const [txt, setTxt] = useState(“Algo de texto”);
	const [a, setA] = useState(0);
	const [b, setB] = useState(0);
    
	const sum = useMemo(() =&gt; {
	console.log('Calculando suma...');
	  return a + b;
	}, [a, b]);

	return (
	  &lt;div&gt;
		&lt;p&gt;Texot: {txt}&lt;/p&gt;
		&lt;p&gt;a: {a}&lt;/p&gt;
		&lt;p&gt;b: {b}&lt;/p&gt;
		&lt;p&gt;sum: {sum}&lt;/p&gt;
		&lt;button onClick={() =&gt; setTxt(“Nuevo Texto!”)}&gt;Escribir Texto&lt;/button&gt;
		&lt;button onClick={() =&gt; setA(a + 1)}&gt;Incrementar a&lt;/button&gt;
		&lt;button onClick={() =&gt; setB(b + 1)}&gt;Incrementar b&lt;/button&gt;
	  &lt;/div&gt;
	);
}</code></pre><p>En nuestro componente Ejemplo de arriba, asumimos que la función <strong>sum()</strong> ejecuta un cálculo costoso. Si el estado <strong>txt</strong> se actualiza, React va a re-renderizar nuestro componente, pero como memoizamos el valor devuelto de sum, esta función no se ejecutará esta vez nuevamente.</p><p>La única vez que la función <strong>sum()</strong> se ejecutará es si el estado <strong>a</strong> o <strong>b</strong> ha sido mutado (cambiado). Este una mejora excelente sobre el comportamiento predeterminado, el cual re-ejecutará este método sobre cada re-renderizado.</p><h2 id="cach-en-react-con-el-hook-usecallback"><strong>Caché en React con el hook useCallback</strong></h2><p>useCallback es útil cuando necesitas pasar una función como una propiedad a un componente hijo, y quieres asegurarte que la referencia de la función no cambia innecesariamente. Al memoizar la función usando useCallback, puedes asegurarte que la referencia de la función sigue siendo siempre el mismo y cuando sus dependencias no cambien.</p><p>Sin entrar demasiado en profundidad en las referencias de función de JavaScript, vamos a ver cómo pueden afectar la renderización de tu aplicación de React. Cuando una referencia de una función cambia, cualquier componente hijo que recibe la función como una propiedad se re-renderizará, inclusive si la lógica de la función misma no ha cambiado. </p><p>Esto se debe a que, como ya mencionamos, React hace una comparación superficial de los valores de las propiedades para determinar si un componente debería re-renderizarse, y una nueva referencia de una función siempre será considerada como un valor diferente al anterior.</p><p>En otras palabras, el simple acto de re-declarar una función (inclusive la misma función exacta), causa que la referencia cambie, y causará que el componente hijo que recibe la función como una propiedad se renderice innecesariamente.</p><p>Aquí hay un bloque de código de ejemplo que refleja una implementación básica de useCallback:</p><pre><code class="language-jsx">import React, { useState, useCallback } from 'react';

function ChildComponent({ onClick }) {
  console.log('ChildComponent se renderiza');
  return (
	&lt;button onClick={onClick}&gt;Hazme clic&lt;/button&gt;
  );
}

function Example() {
  const [count, setCount] = useState(0);
  const [txt, setTxt] = useState(“Algo de texto…”);
  const incrementCount = useCallback(() =&gt; {
    setCount(prevCount =&gt; prevCount + 1);
  }, [setCount]);

  return (
	&lt;div&gt;
	  &lt;p&gt;Texto: {txt}&lt;/p&gt;
	  &lt;p&gt;Contador: {count}&lt;/p&gt;
	  &lt;button onClick={setTxt}&gt;Escribir Texto&lt;/button&gt;
	  &lt;button onClick={setCount}&gt;Incrementar&lt;/button&gt;
	  &lt;ChildComponent onClick={incrementCount} /&gt;
    &lt;/div&gt;
  );
}</code></pre><p>Como puedes ver en el ejemplo de arriba, pasamos el método <strong>incrementCount</strong> en vez del método <strong>setCount</strong> al componente hijo. Esto se debe a que <strong>incrementCount</strong> es memoizado, y cuando ejecutamos nuestro método <strong>setTxt</strong>, no hará que el componente hijo innecesariamente &nbsp;re-renderice.</p><p>La única manera que nuestro componente hijo se vuelva a renderizar en este ejemplo es si el método <strong>setCount</strong> se ejecuta, porque le pasamos como un parámetro de dependencia a nuestra memoización <strong>useCallback</strong>.</p><h2 id="conclusi-n"><strong>Conclusión</strong></h2><p>Almacenar en la caché es una técnica importante para optimizar aplicaciones de React. Al reducir re-renderizados innecesarios, el almacenamiento en caché puede ayudar en mejorar el rendimiento y eficiencia de tu aplicación.</p><p>React provee un comportamiento de almacenamiento en caché predeterminado al usar un DOM virtual para comparar los cambios en el estado y las props, y actualizar los componentes solamente después de que una comparación superficial refleje cambios. Este es una gran técnica de optimización que es suficiente en muchos escenarios, pero a veces se desea más control detallado.</p><p>Los hooks useMemo y useCallback fueron creados para alcanzar este control detallado.</p><p>useMemo se usa para memoizar los <em>resultados</em> de una llamada de función, y es útil cuando la función es costosa para ejecutar y el resultado no cambia con frecuencia.</p><p>useCallback se usa para memoizar la referencia actual de una función en vez del valor devuelto, y se usa cuando la función se pasa como una prop a componentes hijos que pueden causar re-renderizados innecesarios.</p><p>¿Quieres aprender más? Para aprender más visita mi <a href="https://www.ohmycrawl.com/blog/">Blog OhMyCrawl</a> para más consejos de programación para SEO.</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Cómo utilizar la API React context en tus proyectos ]]>
                </title>
                <description>
                    <![CDATA[ La gestión del estado es una parte esencial del desarrollo de aplicaciones en React. Una forma común de gestionar el estado es pasando props. Pasar props significa enviar datos de un componente a otro. Es una buena manera de asegurarse de que los datos lleguen al lugar correcto en una ]]>
                </description>
                <link>https://www.freecodecamp.org/espanol/news/como-usar-context-api-de-react-en-tus-proyectos/</link>
                <guid isPermaLink="false">65c5b8a185c7c103ecb57c94</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Cristian Fernando Villca Gutierrez ]]>
                </dc:creator>
                <pubDate>Fri, 29 Mar 2024 00:06:40 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/espanol/news/content/images/2024/02/context-api-cover-main-1.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>Artículo original:</strong> <a href="https://www.freecodecamp.org/news/context-api-in-react/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Use the React Context API in Your Projects</a>
      </p><p>La gestión del estado es una parte esencial del desarrollo de aplicaciones en React. Una forma común de gestionar el estado es pasando props. Pasar props significa enviar datos de un componente a otro. Es una buena manera de asegurarse de que los datos lleguen al lugar correcto en una aplicación React.</p><p>Pero puede resultar molesto pasar props cuando tienes que enviar los mismos datos a muchos componentes o cuando los componentes están muy lejos unos de otros. Esto puede hacer que una aplicación sea más lenta y más difícil de trabajar.</p><p>Afortunadamente, React proporciona una función incorporada conocida como Context API que ayuda a "teletransportar" datos a los componentes que los necesitan sin pasar props.</p><p>En este artículo, exploraremos cómo funciona Context API y cómo usarla de manera efectiva en sus aplicaciones React.</p><h2 id="el-problema-al-pasar-props">El problema al pasar props</h2><p>En React, <strong>pasar props es un concepto fundamental</strong> que permite que un componente principal comparta datos con sus componentes secundarios, así como con otros componentes dentro de una aplicación.</p><p>En muchos casos, pasar props puede ser una forma eficaz de compartir datos entre diferentes componentes de su aplicación. Pero pasar props a lo largo de una cadena de múltiples componentes para llegar a un componente específico puede hacer que su código sea demasiado engorroso.<br></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2023/03/image-198.png" class="kg-image" alt="image-198" width="600" height="400" loading="lazy"><figcaption>Pasando props de un componente padre a un componente hijo</figcaption></figure><p><br>Según el diagrama anterior, para pasar datos al componente "Hijo B", debemos pasarlos a través de todos los componentes intermedios, incluso si esos componentes en realidad no usan los datos por sí mismos. Esto es lo que se conoce como "<em>prop drilling</em>".</p><p>El <em>prop drilling</em> puede hacer que tu código sea más difícil de leer y mantener, y también puede dificultar la refactorización de tus componentes más adelante.</p><p>Aquí es donde entra en juego Context API. Con Context API, puede almacenar datos en el nivel superior del árbol de componentes y ponerlos a disposición de todos los demás componentes que los necesiten sin pasar props.</p><h2 id="-c-mo-funciona-context-api">¿Cómo funciona Context API?</h2><p>Context API permite pasar datos a través de un árbol de componentes sin tener que pasar props manualmente en cada nivel. Esto facilita el intercambio de datos entre componentes.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/02/image-197.png" class="kg-image" alt="image-197" srcset="https://www.freecodecamp.org/espanol/news/content/images/size/w600/2024/02/image-197.png 600w, https://www.freecodecamp.org/espanol/news/content/images/2024/02/image-197.png 623w" width="623" height="359" loading="lazy"><figcaption>Funcionamiento de Context API</figcaption></figure><p>Por ejemplo, digamos que tiene una aplicación de compras con un componente que muestra el carrito de compras de un usuario y otro componente que muestra el historial de pedidos del usuario.</p><p>Con Context API, puede crear un "contexto" que contenga la información de compras del usuario, como su carrito y su historial de pedidos. Luego, puede usar ese contexto tanto en el carrito de compras como en el componente del historial de pedidos, sin tener que pasar la información a través de props.</p><p>Es como tener una caja grande que contiene todo lo que necesitas para tu viaje de compras. Puedes sacar cosas de la caja cuando las necesites y volver a guardarlas cuando hayas terminado.</p><p>Básicamente, Context API consta de dos componentes principales: <strong>el proveedor de contexto y el consumidor de contexto</strong>. El proveedor es responsable de crear y gestionar el contexto que contiene los datos que se compartirán entre los componentes. Por otro lado, el consumidor se utiliza para acceder al contexto y sus datos desde dentro de un componente.</p><p>En el ejemplo dado, el proveedor creará el contexto que contiene la información de compras del usuario, mientras que los componentes del consumidor (carrito de compras e historial de pedidos) accederán a ese contexto para recuperar los datos que necesitan. Esto evita la necesidad de transmitir la información a través de props, lo que hace que su código sea más eficiente y más fácil de administrar.</p><h2 id="-c-mo-empezar-a-usar-context-api">¿Cómo empezar a usar context API?</h2><p>Para comenzar a utilizar Context API en sus aplicaciones, deberá seguir unos sencillos pasos:</p><h2 id="crear-un-objeto-de-contexto">Crear un objeto de contexto </h2><p>Primero, necesita crear un objeto de contexto usando la función <code>createContext</code> que viene de manera nativa en React. Este objeto de contexto contendrá los datos que desea compartir en su aplicación.</p><p>Cree un nuevo archivo llamado <code>MiContexto.js</code> en la carpeta <code>src</code> y agregue el siguiente código para crear un objeto de contexto:</p><pre><code class="language-js">import { createContext } from 'react';

export const MiContexto = createContext("");</code></pre><p><br>En el código anterior, importamos <code>createContext</code> desde React y lo usamos para crear un nuevo objeto de contexto llamado "MiContexto". Luego, exportamos el objeto de contexto para poder usarlo en otras partes de nuestra aplicación.</p><h2 id="envolver-componentes-con-un-proveedor">Envolver componentes con un Proveedor</h2><p>Una vez que haya creado un objeto de contexto, debe envolver los componentes que necesitan acceso a los datos compartidos con un componente de proveedor. El componente Proveedor acepta una propiedad <code>value</code> que contiene los datos compartidos, y cualquier componente que sea hijo del componente Proveedor puede acceder a esos datos compartidos.</p><p>Es importante tener en cuenta que el componente Proveedor debe incluirse alrededor del componente de nivel superior en una aplicación para garantizar que todos los componentes secundarios tengan acceso a los datos compartidos.</p><p>A continuación se muestra un ejemplo que demuestra cómo empaquetar componentes con un Proveedor usando Context API:</p><pre><code class="language-js">// Creamos un componente padre que envuelva los componentes hijos con un Proveedor
import { useState, React } from "react";
import { MiContexto } from "./MiContexto";
import MiComponente from "./MiComponente";

function App() {
  const [texto, setTexto] = useState("");

  return (
    &lt;div&gt;
      &lt;MiContexto.Provider value={{ texto, setTexto }}&gt;
        &lt;MiComponente /&gt;
      &lt;/MiContexto.Provider&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre><p>En este ejemplo, tenemos un componente principal llamado <code>App</code>. Este componente tiene una variable de estado llamada "texto", que inicialmente se establece en una cadena vacía. También hemos definido una función llamada <code>setTexto</code> que se puede usar para actualizar el valor del texto.</p><h2 id="consumir-el-contexto">Consumir el contexto</h2><p>Ahora que hemos creado el componente proveedor, necesitamos consumir el contexto en otros componentes. Para hacer esto, usamos el hook "useContext" de React.</p><pre><code class="language-js">import { useContext } from 'react';
import { MiContexto } from './MiContexto';

function MiComponente() {
  const { texto, setTexto } = useContext(MiContexto);

  return (
    &lt;div&gt;
      &lt;h1&gt;{texto}&lt;/h1&gt;
      &lt;button onClick={() =&gt; setTexto('Hola mundo!')}&gt;
        Click me
      &lt;/button&gt;
    &lt;/div&gt;
  );
}

export default MiComponente;</code></pre><p>En este ejemplo, hemos utilizado el hook <code>useContext</code> para acceder a las variables "texto" y "setTexto" que se definieron en el componente del proveedor.</p><p>Dentro de la declaración de retorno de "MiComponente", hemos representado un elemento de párrafo que muestra el valor del texto. También hemos representado un botón que, al hacer clic, llamará a la función <code>setTexto</code> para actualizar el valor del texto a "¡Hola, mundo!".</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/espanol/news/content/images/2024/02/a191j3C-1.gif" class="kg-image" alt="a191j3C-1" width="716" height="472" loading="lazy"></figure><p>¡Y eso es! Así es como puedes usar la Context API en tu aplicación React.</p><p>Al crear un objeto de contexto, definir un componente de proveedor y consumir el contexto en otros componentes, puede compartir datos en su aplicación de una manera simple y eficiente.</p><h2 id="casos-de-uso-de-context-api">Casos de uso de context API</h2><p>A continuación se muestran algunos casos de uso reales de Context API:</p><ol><li><strong>Temas</strong>: puede utilizar Context API para almacenar el tema actual de su aplicación y ponerlo a disposición de todos los componentes. De esta manera, cada vez que el usuario cambie el tema (como habilitar el modo oscuro), todos los componentes se actualizarán con el nuevo tema.</li><li><strong>Autenticación de usuario</strong>: también puede utilizar Context API para almacenar el estado de autenticación de un usuario y transmitirlo a todos los componentes que lo necesitan. De esta manera, puede restringir fácilmente el acceso a ciertas partes de su aplicación según el estado de autenticación del usuario.</li><li><strong>Soporte multilingüe</strong>: puede almacenar el idioma actual de su aplicación en el contexto y transmitirlo a todos los componentes que lo necesiten. De esta manera, puede cambiar fácilmente entre diferentes idiomas sin tener que pasar el idioma como props a todos los componentes.</li><li><strong>Acceso a datos de fuentes externas</strong>: finalmente, puede utilizar Context API para almacenar datos recuperados de fuentes externas, como API o bases de datos, y ponerlos a disposición de todos los componentes. Esto puede simplificar su código y facilitar la administración de datos en su aplicación.</li></ol><h2 id="en-resumen">En resumen</h2><p>En este artículo, exploramos Context API de React, una poderosa herramienta para administrar el estado en aplicaciones React.</p><p>Hemos analizado los conceptos básicos Context API, incluida la creación de un contexto, la creación de un componente de proveedor para pasar datos a componentes secundarios y el consumo de datos en otro componente mediante el hook <code>useContext</code>.</p><h1 id="conclusi-n">Conclusión</h1><p>Si está interesado en explorar cómo implementar un tema de modo claro/oscuro en sus propios proyectos de React usando Context API, he creado un sitio web simple que demuestra cómo hacerlo. Puedes encontrar el código del proyecto en mi <a href="https://github.com/dboatengg/context-api-tutorial">GitHub.</a></p><p>Al explorar el código y experimentar con sus propias modificaciones, estará bien encaminado para dominar Context API y desbloquear todo su potencial en sus propios proyectos.</p><p>¡Muchas gracias por leer!</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
