En esta publicación de blog, repasaremos todo lo que necesita para comprender y crear una aplicación React básica. Ya sea que sea un principiante que acaba de comenzar con React o un desarrollador experimentado que busca mejorar sus habilidades, esta guía es para usted.
Esta guía lo guiará a través de todo el proceso de creación de una aplicación de lista de tareas totalmente funcional, incluido el diseño, el diseño, la administración del estado y más. Usaremos componentes funcionales y ganchos. Aprenderemos cómo usar el estado y los accesorios para pasar datos entre componentes y cómo manejar la entrada del usuario y actualizar el estado de su aplicación.
Al final de esta guía, tendremos una sólida comprensión de cómo crear una aplicación React desde cero, y podrá aprovechar sus nuevos conocimientos para crear sus propios proyectos React.
¡Entonces empecemos!
* Puede encontrar el código de la aplicación que vamos a construir aquí y la versión en vivo aquí .
Usaremos TypeScript para escribir el código y Vite para desarrollar y crear la aplicación.
TypeScript es un lenguaje de programación fuertemente tipado que se basa en JavaScript. En términos prácticos, si ya conoce JavaScript, todo lo que necesita aprender a usar TypeScript es cómo usar tipos e interfaces.
Los tipos e interfaces nos permiten definir los tipos de datos que estamos usando en el código. Con esto, podemos detectar errores desde el principio y evitar problemas en el futuro.
Por ejemplo, si una función toma un number
pero le pasamos una string
, TypeScript se quejará de inmediato:
const someFunc = (parameter: number) => {...}; someFunc('1') // Argument of type 'string' is not assignable to parameter of type 'number'.
Si estuviéramos usando JavaScript, probablemente solo detectaríamos el error más adelante.
No siempre necesitamos especificar el tipo, ya que TypeScript puede inferirlos automáticamente la mayoría de las veces.
Puede aprender los conceptos básicos de TypeScript aquí . (O simplemente ignore los tipos).
La forma más común de activar una aplicación React probablemente sea usando create-react-app . Usaremos Vite (pronunciado como "veet") en su lugar. Pero no se preocupe, es igual de simple, pero más eficiente.
Con herramientas como webpack (utilizado por create-react-app bajo el capó), toda su aplicación debe estar agrupada en un solo archivo antes de que pueda ser enviada al navegador. Vite, por otro lado, aprovecha los módulos ES nativos en el navegador para hacer que la agrupación sea más eficiente con Rollup , sirviendo partes del código fuente según sea necesario.
Vite también puede acelerar en gran medida el tiempo de desarrollo con Hot Module Replacement, lo que significa que cada vez que se realizan cambios en el código fuente, solo se actualizan los cambios, en lugar de toda la aplicación.
Además de eso, Vite ofrece soporte nativo para Typescript, JSX y TSX, CSS y más.
De manera similar a create-react-app, Vite ofrece una herramienta llamada create-vite, que nos permite comenzar rápidamente un nuevo proyecto usando plantillas básicas, incluidas opciones para Vanilla JS, o usando bibliotecas como React.
Para ser claros, no necesitamos una herramienta como Vite o create-react-app para crear aplicaciones React, pero nos facilitan la vida al encargarse de configurar el proyecto, empaquetar el código, usar transpiladores y mucho más.
React nos permite agregar marcado directamente en el código que luego se compilará en JavaScript simple. Esto se llama JSX . Cuando usamos JSX, podemos guardar nuestros archivos como .jsx para JavaScript o .tsx para TypeScript.
Se parece a esto:
const element = <h1>Hello, world!</h1>;
Es similar a HTML, pero está incrustado en el archivo JavaScript y nos permite manipular el marcado con lógica de programación. También podemos agregar código JavaScript dentro de JSX, siempre que esté entre llaves.
Por ejemplo, si tenemos una matriz de texto que queremos representar como elementos de párrafo diferentes, podemos hacer esto:
const paragraphs = ["First", "Second", "Third"]; paragraphs.map((paragraph) => <p>{paragraph}</p>);
Y sería compilado a algo como esto:
<p>First</p> <p>Second</p> <p>Third</p>
Pero si tratamos de hacer precisamente eso, no funcionará. Esto se debe a que React funciona con componentes y JSX debe renderizarse dentro de estos componentes.
Los componentes de React se pueden escribir usando clases de JavaScript o simplemente funciones simples. Nos centraremos en los componentes de función, ya que son la forma más actualizada y recomendada de escribir componentes de React en la actualidad.
Un componente se define mediante una función que devolverá el JSX que el navegador compilará y procesará. Entonces, para extender el ejemplo anterior, si queremos representar los elementos del párrafo, se vería así:
// Define the component const Component = () => { const paragraphs = ["First", "Second", "Third"]; return ( <> {paragraphs.map((paragraph) => ( <p>{paragraph}</p> ))} </> ); }; // Use the component in the same way you use an HTML element in the JSX const OtherComponent = () => { return <Component />; };
Ahora tal vez queramos reutilizar este componente con información diferente. Podemos hacerlo usando accesorios, que es solo un objeto de JavaScript que contiene algunos datos.
En nuestro ejemplo, en lugar de codificar la matriz, podríamos pasarla al componente. El resultado será el mismo, pero ahora el componente será reutilizable.
Si usamos TypeScript, debemos especificar los tipos de datos dentro del objeto props (no hay contexto para lo que son, por lo que TypeScript no puede inferirlos), que en este caso es una matriz de cadenas ( string[]
).
const Component = (props: { paragraphs: string[] }) => { <> {props.paragraphs.map((paragraph) => ( <p>{paragraph}</p> ))} </>; }; const OtherComponent = () => { const paragraphs = ["First", "Second", "Third"]; return <Component paragraphs={paragraphs} />; };
Si queremos hacer un componente interactivo, necesitaremos almacenar información en el estado del componente, para que pueda "recordarlo".
Por ejemplo, si queremos definir un contador simple que muestre la cantidad de veces que se hace clic en un botón, necesitamos una forma de almacenar y actualizar este valor. React nos permite hacerlo con el gancho useState (un gancho es una función que le permite "engancharse" al estado de React y las características del ciclo de vida ).
Llamamos al gancho useState
con el valor inicial, y nos devuelve una matriz con el valor en sí y una función para actualizarlo.
import { useState } from "react"; const Counter = () => { const [count, setCount] = useState(0); return ( <> <span>{count}</span> <button onClick={() => setCount(count + 1)}>Increment count</button> </> ); };
Con este conocimiento, ahora estamos listos para comenzar a construir nuestra aplicación React.
Para usar Vite vamos a necesitar **nodo **y un administrador de paquetes.
Para instalar el nodo, simplemente elija una de las opciones aquí según su sistema y configuraciones. Si está usando Linux o una Mac, también puede instalarlo usando Homebrew .
El administrador de paquetes puede ser npm o yarn . En esta publicación vamos a usar npm .
A continuación, es el momento de crear el proyecto. En la terminal, navegamos al directorio donde se creará el proyecto, luego ejecutamos el comando create-vite.
$ npm create vite@latest
Es posible que se nos solicite instalar paquetes adicionales (como create-vite). Escriba y
y presione enter para continuar.
Need to install the following packages: [email protected] Ok to proceed? (y)
A continuación, se nos pedirá que ingresemos la información del proyecto.
Introduzca el nombre del proyecto. Elegí my-react-project
.
? Project name: › my-react-project
Seleccione React como el "marco".
React es técnicamente una biblioteca y no un marco , pero no te preocupes por eso.
? Select a framework: › - Use arrow-keys. Return to submit. Vanilla Vue ❯ React Preact Lit Svelte Others
Seleccione TypeScript + SWC como variante.
SWC (siglas de Speedy Web Compiler) es un compilador superrápido de TypeScript/JavaScript escrito en Rust. Afirman ser "20 veces más rápido que Babel en un solo hilo y 70 veces más rápido en cuatro núcleos".
? Select a variant: › - Use arrow-keys. Return to submit. JavaScript TypeScript JavaScript + SWC ❯ TypeScript + SWC
Listo, el proyecto está creado. Para iniciarlo en modo de desarrollo, debemos cambiar al directorio del proyecto, instalar las dependencias y ejecutar el comando dev script.
cd my-react-project npm install npm run dev
Después de unos segundos, veremos algo similar a esto:
VITE v4.0.4 ready in 486 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose ➜ press h to show help
Si abrimos nuestro navegador y navegamos a http://localhost:5173 / veremos la página predeterminada de Vite + React:
Esto significa que todo es como debe ser y podemos comenzar a trabajar en nuestra aplicación.
Si abrimos el proyecto en nuestro editor de código o IDE de elección, deberíamos ver una estructura de archivos como esta:
Podemos eliminar algunos de los archivos repetitivos, ya que no los usaremos (todos los archivos .svg y .css).
El código en la función de la aplicación se puede eliminar para dejarnos esto:
function App() { return ( ) } export default App
Volveremos a este archivo más tarde.
El estilo no es el enfoque aquí, pero usaremos Tailwind CSS, que es una biblioteca que nos permite diseñar elementos HTML al agregarles clases. Siga estas instrucciones para ver los estilos reflejados en su propio proyecto.
De lo contrario, puede ignorar las clases en el código.
El proceso de diseño es una parte integral del desarrollo de una aplicación y no debe pasarse por alto.
Para construir nuestra aplicación de lista de tareas, primero debemos pensar en el diseño de los componentes.
Comenzamos simulando una interfaz de usuario básica y describiendo una jerarquía de los componentes involucrados.
Si no eres diseñador, no es necesario que sea perfecto o la interfaz de usuario final en términos de colores y ubicación exacta todavía; es más importante pensar en la estructura de los componentes.
Lo ideal es que nuestros componentes sean responsables de una sola cosa, siguiendo el principio de responsabilidad única .
En la imagen a continuación, los nombres en púrpura son los componentes que vamos a construir; todo lo demás son elementos HTML nativos. Si están uno dentro del otro, significa que probablemente habrá una relación padre-hijo.
Después de tener un boceto, podemos comenzar a construir una versión estática de la aplicación. Es decir, solo los elementos de la interfaz de usuario, pero sin interactividad todavía. Esta parte es bastante sencilla e implica escribir mucho y pensar poco una vez que aprendes a hacerlo.
Puedes encontrar el código de la versión estática en este repositorio de GitHub , en la rama “static-version”. El código para la aplicación completamente funcional es la rama principal.
Envase
Como se describió anteriormente, vamos a tener un Contenedor que se reutilizará para cada sección de la aplicación. Este Contenedor muestra una de las formas de componer diferentes elementos: haciéndolos pasar como hijos.
// src/components/Container.tsx const Container = ({ children, title, }: { children: JSX.Element | JSX.Element[]; title?: string; }) => { return ( <div className="bg-green-600 p-4 border shadow rounded-md"> {title && <h2 className="text-xl pb-2 text-white">{title}</h2>} <div>{children}</div> </div> ); }; export default Container;
Toma un objeto props con un parámetro children
de tipo JSX.Element | JSX.Element[]
. Esto significa que podemos componerlo con cualquier otro elemento HTML o cualquier otro componente que creemos. Se puede representar donde queramos dentro del contenedor, en este caso dentro del segundo div.
En nuestra aplicación, representará cada sección (definida a continuación) cuando las usemos dentro del componente de la aplicación.
El Contenedor también toma una propiedad string
opcional llamada title
, que se representará dentro de un h2 siempre que exista.
// src/App.tsx import Container from "./components/Container"; import Input from "./components/Input"; import Summary from "./components/Summary/Summary"; import Tasks from "./components/Tasks/Tasks"; function App() { return ( <div className="flex justify-center m-5"> <div className="flex flex-col items-center"> <div className="sm:w-[640px] border shadow p-10 flex flex-col gap-10"> <Container title={"Summary"}> <Summary /> </Container> <Container> <Input /> </Container> <Container title={"Tasks"}> <Tasks /> </Container> </div> </div> </div> ); } export default App;
Resumen
La primera sección es un resumen (componente Resumen) que muestra tres elementos (SummaryItem): el número total de tareas, el número de tareas pendientes y el número de tareas completadas. Esta es otra forma de componer componentes: simplemente utilícelos en la declaración de retorno de otro componente.
(Sin embargo, es importante nunca definir un componente dentro de otro componente, ya que eso puede conducir a representaciones y errores innecesarios).
Por ahora solo podemos usar datos estáticos en los dos componentes.
// src/components/Summary/SummaryItem.tsx const SummaryItem = ({ itemName, itemValue, }: { itemName: string; itemValue: number; }) => { return ( <article className="bg-green-50 w-36 rounded-sm flex justify-between p-2"> <h3 className="font-bold">{itemName}</h3> <span className="bg-green-900 text-white px-2 rounded-sm"> {itemValue} </span> </article> ); }; export default SummaryItem; // src/components/Summary/Summary.tsx import SummaryItem from "./SummaryItem"; const Summary = () => { return ( <> <div className="flex justify-between"> <SummaryItem itemName={"Total"} itemValue={3} /> <SummaryItem itemName={"To do"} itemValue={2} /> <SummaryItem itemName={"Done"} itemValue={1} /> </div> </> ); }; export default Summary;
Notará que SummaryItem toma dos accesorios: itemName
, de tipo cadena, y itemValue
, de tipo number
. Estos accesorios se pasan cuando el componente SummaryItem se usa dentro del componente Summary y luego se procesan en SummaryItem JSX.
Tareas
De manera similar, para la sección de tareas (la última) tenemos un componente Tareas que representa los componentes TaskItem.
También con datos estáticos por ahora. Más tarde necesitaremos pasar un nombre de tarea y un estado como accesorios al componente TaskItem para que sea reutilizable y dinámico.
// src/components/Tasks/TaskItem.tsx const TaskItem = () => { return ( <div className="flex justify-between bg-white p-1 px-3 rounded-sm"> <div className="flex gap-2 items-center"> <input type="checkbox" /> Task name </div> <button className="bg-green-200 hover:bg-green-300 rounded-lg p-1 px-3"> Delete </button> </div> ); }; export default TaskItem; // src/components/Tasks/Tasks.tsx import TaskItem from "./TaskItem"; const Tasks = () => { return ( <div className="flex flex-col gap-2"> <TaskItem /> </div> ); }; export default Tasks;
Aporte
Finalmente, el componente Entrada es un formulario con una etiqueta, una entrada de tipo texto y un botón para “Agregar tarea”. Por ahora no hace nada, pero pronto cambiaremos eso.
// src/components/Input.tsx const InputContainer = () => { return ( <form action="" className="flex flex-col gap-4"> <div className="flex flex-col"> <label className="text-white">Enter your next task:</label> <input className="p-1 rounded-sm" /> </div> <button type="button" className="bg-green-100 rounded-lg hover:bg-green-200 p-1" > Add task </button> </form> ); }; export default InputContainer;
Para agregar interactividad en React, necesitamos almacenar información en el estado del componente.
Pero antes de hacer eso, debemos pensar en cómo queremos que cambien los datos con el tiempo. Necesitamos identificar una representación mínima de estos datos e identificar qué componentes debemos usar para almacenar este estado.
Una representación mínima del estado
El estado debe contener toda la información necesaria para que nuestra aplicación sea interactiva, pero nada más. Si podemos calcular un valor a partir de un valor diferente, debemos mantener solo uno de ellos en el estado. Esto hace que nuestro código no solo sea menos detallado, sino también menos propenso a errores que involucran valores de estado contradictorios.
En nuestro ejemplo, podríamos pensar que necesitamos realizar un seguimiento de los valores de las tareas totales, las tareas pendientes y las tareas realizadas.
Pero para realizar el seguimiento de las tareas, basta con tener un solo arreglo con objetos que representen cada tarea y su estado (pendiente o finalizada).
const tasks = [ { name: "task one", done: false, }, { name: "task two", done: true, }, ];
Con estos datos, siempre podemos encontrar toda la otra información que necesitamos en el momento del renderizado utilizando métodos de matriz. También evitamos la posibilidad de contradicciones, como tener un total de 4 tareas, pero solo 1 pendiente y 1 tarea finalizada, por ejemplo.
También necesitamos estado en nuestro formulario (en el componente de entrada) para que podamos hacerlo interactivo.
Donde el estado debe vivir
Piénselo de esta manera: ¿qué componentes necesitan acceder a los datos que vamos a almacenar en el estado? Si es un solo componente, el estado puede vivir en este componente mismo. Si hay más de un componente que necesita los datos, entonces debe encontrar el padre común para estos componentes.
En nuestro ejemplo, solo se debe acceder al estado necesario para controlar el componente de entrada allí, por lo que puede ser local para este componente.
// src/components/Input.tsx import { useState } from "react"; const InputContainer = () => { const [newTask, setNewTask] = useState(""); // Initialize newTask and setNewTask return ( <form action="" className="flex flex-col gap-4"> <div className="flex flex-col"> <label className="text-white">Enter your next task:</label> <input className="p-1 rounded-sm" type="text" value={newTask} // Set the input value to newTask onChange={(e) => setNewTask(e.target.value)} // Set newTask to the input value whenever the user types something /> </div> <button type="submit" className="bg-green-100 rounded-lg hover:bg-green-200 p-1" > Add task </button> </form> ); }; export default InputContainer;
Lo que esto hace es mostrar nuestro valor newTask
en la entrada y llamar a la función setNewTask
cada vez que cambia la entrada (es decir, cuando el usuario escribe algo).
No veremos cambios inmediatos en la interfaz de usuario, pero esto es necesario para que podamos controlar la entrada y tener acceso a su valor para usarlo más tarde.
Sin embargo, el estado para realizar un seguimiento de las tareas debe manejarse de manera diferente, ya que debe accederse a él en los componentes SummaryItem (necesitamos mostrar el número de tareas totales, pendientes y realizadas), así como en los componentes TaskItem (necesitamos para mostrar las tareas en sí). Y debe estar en el mismo estado porque esta información siempre debe estar sincronizada.
Echemos un vistazo a nuestro árbol de componentes (puede usar las herramientas de desarrollo de React para esto).
Podemos ver que el primer componente principal común es App. Así que aquí es donde va a vivir nuestro estado para las tareas.
Con el estado en su lugar, todo lo que quedará será pasar los datos como accesorios a los componentes que necesitan usarlos.
(Todavía no nos preocupa cómo realizar y persistir los cambios en el estado principal, esa es la siguiente parte).
// src/App.tsx import { useState } from "react"; import { v4 as uuidv4 } from "uuid"; import Container from "./components/Container"; import Input from "./components/Input"; import Summary from "./components/Summary/Summary"; import Tasks from "./components/Tasks/Tasks"; export interface Task { name: string; done: boolean; id: string; } const initialTasks = [ { name: "task one", done: false, id: uuidv4(), }, { name: "task two", done: true, id: uuidv4(), }, ]; function App() { const [tasks, setTasks] = useState<Task[]>(initialTasks); return ( <div className="flex justify-center m-5"> <div className="flex flex-col items-center"> <div className="border shadow p-10 flex flex-col gap-10 sm:w-[640px]"> <Container title={"Summary"}> <Summary tasks={tasks} /> </Container> <Container> <Input /> </Container> <Container title={"Tasks"}> <Tasks tasks={tasks} /> </Container> </div> </div> </div> ); } export default App;
Aquí estamos inicializando el valor de las tareas con datos ficticios ( initialTasks
), solo para que podamos visualizarlo antes de que finalice la aplicación. Más tarde, podemos cambiarlo a una matriz vacía, por lo que un nuevo usuario no verá ninguna tarea al abrir la aplicación desde cero.
Además del name
y las propiedades done
, también estamos agregando una identificación a nuestros objetos de tareas, ya que será necesario en breve.
Estamos definiendo una interface
con los tipos del valor en los objetos de la tarea y pasándola a la función useState
. Esto es necesario en este caso, ya que TypeScript no podrá inferirlo cuando cambiamos el valor inicial de las tasks
a una matriz vacía, o cuando lo pasamos como accesorios.
Finalmente, observe que estamos pasando las tareas como accesorios a los componentes Resumen y Tareas. Estos componentes deberán cambiarse para adaptarse a eso.
// src/components/Summary/Summary.tsx import { Task } from "../../App"; import SummaryItem from "./SummaryItem"; const Summary = ({ tasks }: { tasks: Task[] }) => { const total = tasks.length; const pending = tasks.filter((t) => t.done === false).length; const done = tasks.filter((t) => t.done === true).length; return ( <> <div className="flex flex-col gap-1 sm:flex-row sm:justify-between"> <SummaryItem itemName={"Total"} itemValue={total} /> <SummaryItem itemName={"To do"} itemValue={pending} /> <SummaryItem itemName={"Done"} itemValue={done} /> </div> </> ); }; export default Summary;
Actualizamos el componente Resumen para que ahora acepte tasks
como accesorio. También definimos el valor total
, pending
y done
, que se transmitirá como accesorios a los componentes itemValue
en lugar de los elementos estáticos que teníamos antes.
// src/components/Tasks/Tasks.tsx import { Task } from "../../App"; import TaskItem from "./TaskItem"; const Tasks = ({ tasks }: { tasks: Task[] }) => { return ( <div className="flex flex-col gap-2"> {tasks.map((t) => ( <TaskItem key={t.id} name={t.name} /> ))} </div> ); }; export default Tasks; // src/components/Tasks/TaskItem.tsx import { useState } from "react"; const TaskItem = ({ name }: { name: string }) => { const [done, setDone] = useState(false); return ( <div className="flex justify-between bg-white p-1 px-3 rounded-sm gap-4"> <div className="flex gap-2 items-center"> <input type="checkbox" checked={done} onChange={() => setDone(!done)} /> {name} </div> <button className="bg-green-200 hover:bg-green-300 rounded-lg p-1 px-3"> Delete </button> </div> ); }; export default TaskItem;
Para el componente Tasks, también tomamos task
s como accesorio y asignamos su propiedad de name
a los componentes TaskItem. Como resultado, obtenemos un componente TaskItem para cada objeto dentro de la matriz de tasks
. También actualizamos el componente TaskItem para aceptar el name
como accesorio.
Aquí es donde la identificación es útil, ya que necesitamos pasar una clave única cada vez que tenemos una lista de componentes secundarios. Si no agregamos la clave, esto podría provocar errores en la renderización . (En una aplicación de producción, lo más probable es que la identificación provenga del backend).
El resultado por ahora es este:
Ya podemos ver los números de resumen y los nombres de las tareas que reflejan nuestros datos ficticios. Pero todavía nos falta una forma de agregar o eliminar tareas.
Agregar flujo de datos inverso
Para finalizar nuestra aplicación, necesitamos una forma de cambiar el estado del componente de la aplicación (donde están los datos de las tareas) de los componentes secundarios Input y TaskItem.
Para hacer eso, podemos usar las funciones generadas por el useState
para definir controladores de eventos y pasarlos como accesorios. Una vez que hacemos eso, simplemente los llamamos durante la interacción del usuario apropiada desde los componentes secundarios.
Asegúrese de nunca cambiar el estado cada vez que lo actualice , ya que esto provocará errores. Siempre reemplace el objeto de estado por uno nuevo cuando lo actualice.
A continuación se muestra nuestro componente de aplicación final con los controladores declarados y transmitidos como apoyos a los componentes de entrada y tareas.
handleSubmit
devuelve una nueva matriz con las tareas antiguas más la nueva. toggleDoneTask
devuelve una nueva matriz con la propiedad done
opuesta, para el id
especificado. handleDeleteTask
devuelve una nueva matriz sin la tarea con la id
especificada.
// src/App.tsx import { FormEvent, useState } from "react"; import { v4 as uuidv4 } from "uuid"; import Container from "./components/Container"; import Input from "./components/Input"; import Summary from "./components/Summary/Summary"; import Tasks from "./components/Tasks/Tasks"; export interface Task { name: string; done: boolean; id: string; } function App() { const [tasks, setTasks] = useState<Task[]>([]); const handleSubmit = (e: FormEvent<HTMLFormElement>, value: string) => { e.preventDefault(); const newTask = { name: value, done: false, id: uuidv4(), }; setTasks((tasks) => [...tasks, newTask]); }; const toggleDoneTask = (id: string, done: boolean) => { setTasks((tasks) => tasks.map((t) => { if (t.id === id) { t.done = done; } return t; }) ); }; const handleDeleteTask = (id: string) => { setTasks((tasks) => tasks.filter((t) => t.id !== id)); }; return ( <div className="flex justify-center m-5"> <div className="flex flex-col items-center"> <div className="border shadow p-10 flex flex-col gap-10 sm:w-[640px]"> <Container title={"Summary"}> <Summary tasks={tasks} /> </Container> <Container> <Input handleSubmit={handleSubmit} /> </Container> <Container title={"Tasks"}> <Tasks tasks={tasks} toggleDone={toggleDoneTask} handleDelete={handleDeleteTask} /> </Container> </div> </div> </div> ); } export default App;
Este es el componente de entrada final que usa handleSubmit
para actualizar el estado del componente de la aplicación.
// src/components/Input.tsx import { FormEvent, useState } from "react"; const InputContainer = ({ handleSubmit, }: { handleSubmit: (e: FormEvent<HTMLFormElement>, value: string) => void; }) => { const [newTaskName, setNewTaskName] = useState(""); return ( <form action="" className="flex flex-col gap-4" onSubmit={(e) => { handleSubmit(e, newTaskName); setNewTaskName(""); }} > <div className="flex flex-col"> <label className="text-white">Enter your next task:</label> <input className="p-1 rounded-sm" type="text" value={newTaskName} onChange={(e) => setNewTaskName(e.target.value)} /> </div> <button type="submit" className="bg-green-100 rounded-lg hover:bg-green-200 p-1" > Add task </button> </form> ); }; export default InputContainer;
Este es el componente Tareas final, que actualizamos para pasar los accesorios de la aplicación a TaskItem. También agregamos un operador ternario para devolver "¡No hay tareas todavía!" cuando no hay tareas.
// src/components/Tasks/Tasks.tsx import { Task } from "../../App"; import TaskItem from "./TaskItem"; const Tasks = ({ tasks, toggleDone, handleDelete, }: { tasks: Task[]; toggleDone: (id: string, done: boolean) => void; handleDelete: (id: string) => void; }) => { return ( <div className="flex flex-col gap-2"> {tasks.length ? ( tasks.map((t) => ( <TaskItem key={t.id} name={t.name} done={t.done} id={t.id} toggleDone={toggleDone} handleDelete={handleDelete} /> )) ) : ( <span className="text-green-100">No tasks yet!</span> )} </div> ); }; export default Tasks;
Y este es el componente TaskItem final, usando toggleDone
y handleDelete
para actualizar el estado del componente de la aplicación.
// src/components/Tasks/TaskItem.tsx const TaskItem = ({ name, done, id, toggleDone, handleDelete, }: { name: string; done: boolean; id: string; toggleDone: (id: string, done: boolean) => void; handleDelete: (id: string) => void; }) => { return ( <div className="flex justify-between bg-white p-1 px-3 rounded-sm gap-4"> <div className="flex gap-2 items-center"> <input type="checkbox" checked={done} onChange={() => toggleDone(id, !done)} /> {name} </div> <button className="bg-green-200 hover:bg-green-300 rounded-lg p-1 px-3" type="button" onClick={() => handleDelete(id)} > Delete </button> </div> ); }; export default TaskItem;
¡Y aquí está nuestra aplicación final después de agregar algunas tareas!
Si está programando, puede implementar su propia aplicación siguiendo estas instrucciones .
Puede encontrar el repositorio con todo el código que revisamos aquí , y la versión en vivo de la aplicación aquí .
En conclusión, crear una aplicación de lista de tareas puede ser una excelente manera de aprender y solidificar nuestra comprensión de React y sus principios. Al dividir el proceso en pequeños pasos y seguir las mejores prácticas, podemos crear una aplicación funcional en un período de tiempo relativamente corto.
Hemos cubierto:
los conceptos clave de componentes, estado y flujo de datos inverso.
el diseño y la arquitectura de la aplicación.
mejores prácticas como el principio de responsabilidad única
Al seguir los pasos descritos en esta guía, ahora debería tener una comprensión sólida de cómo crear una aplicación React simple y poder aplicarla a sus propios proyectos.
¡Feliz codificación!