React Manejo Eventos
React Manejo Eventos
Manejar eventos en React es muy similar a manejar eventos en el DOM, solo necesitamos pasarle
una propiedad on + evento, por ejemplo: onClick, onChange, onMouseOver, que será igual a una
función en la que estará el código que se ejecutará cuando ocurra dicho evento.
A diferencia de los eventos del DOM, para manejar eventos en React tenemos unas pequeñas
diferencias en la sintaxis:
En React no tenemos que ejecutar el código nosotros, React ya maneja esto por debajo, solo es
necesario pasar una función, React solito la ejecutará cuando ocurra el evento que estemos
escuchando.
Dentro de estos eventos también puedes recibir como parámetro la información del evento, en
donde puedes encontrar propiedades muy interesantes, como por ejemplo, el valor de algún
input, con event.target.value.
Manejo del estado
El estado en React nos ayuda a crear datos mutables o datos que pueden ser modificados.
Para manejar el estado depende del si nuestro componente es generado con una clase o si es un
componente funcional.
1. Clase: Para manejar el estado dentro de una clase podemos crearlo en el constructor de la
clase, y para actualizarlo utilizamos el setter setState
2. Función Si estamos en un componente funcional podemos utilizar el hook de estado, que
nos regresará arreglo con un getter (que será el valor de nuestro estado) y un setter (una
función para actualizar el estado).
Los Hooks son funciones que te permiten enganchar el estado de React y el ciclo de vida desde
componentes funcionales, entre muchas otras cosas. Nos permiten usar React sin clases.
Para manejar el estado en componentes clase necesitamos crearlo como una propiedad dentro de
nuestro componente clase, usamos this.state, y para actualizar el estado, la clase de React ya tiene
un setter: this.setState. (Dentro de este tipo de componentes no se pueden usar los hooks).
Estado en componentes funcionales
El manejo del estado en estos componentes es mucho más sencillo, utilizando el hook de estado.
Una vez lo importamos ya podemos usarlo en nuestro componente, este hook nos regresará un
array con dos elementos:
También podemos pasarle un valor inicial a nuestro estado dentro de los paréntesis.
Stateless: son componentes que no tienen ningún tipo de estado declarado en el cuerpo de la
función.
Contando y buscando TODOs
El levantamiento de estado es una técnica de React que pone el estado en una localización donde
se pueda pasar como props a los componentes.
Lo ideal es poner el estado en el lugar más cercano a todos los componentes que quieren
compartir esa información, así todos nuestros componentes tendrán el mismo estado y cuando
este cambie sólo re-renderizará lo necesario.
Esto es justamente lo que haremos ahora para hacer funcionar nuestro contador y nuestro
buscador, pero debemos tener mucho cuidado al manejar re-renderizados, porque estos pueden
causar una mala experiencia de usuario o incluso romper nuestra aplicación.
Contando TODOs
Dentro de nuestro componente App.js primero necesitamos crear el estado de nuestros TODOs,
para a partir de ahí poder saber cuántos TODOs existen y cuántos están completados.
const defaultTodos = [
{ text: 'Cortar cebolla', completed: true },
{ text: 'Tomar el cursso de intro a React', completed: false },
{ text: 'Llorar con la llorona', completed: true },
{ text: 'LALALALAA', completed: false },
];
function App() {
// Estado inicial de nuestros TODOs
const [todos, setTodos] = React.useState(defaultTodos);
// Cantidad de TODOs completados
const completedTodos = todos.filter(todo => !!todo.completed).length;
// Cantidad total de TODOs
const totalTodos = todos.length;
return (
<React.Fragment>
{/* Pasamos el estado a nuestro componente */}
<TodoCounter
total={totalTodos}
completed={completedTodos}
/>
<TodoSearch />
<TodoList>
{searchedTodos.map(todo => (
<TodoItem
key={todo.text}
text={todo.text}
completed={todo.completed}
/>
))}
</TodoList>
<CreateTodoButton />
</React.Fragment>
);
}
Una vez teniendo estos datos, ya podemos recibir las props en nuestro contador.
Buscando TODOs
Para esto haremos algo parecido a lo que hicimos para contar nuestros TODOs, para tener acceso
al valor de la búsqueda desde nuestro componente App.js y ahí filtrar nuestros TODOs.
const defaultTodos = [
{ text: 'Cortar cebolla', completed: true },
{ text: 'Tomar el cursso de intro a React', completed: false },
{ text: 'Llorar con la llorona', completed: true },
{ text: 'LALALALAA', completed: false },
];
function App() {
const [todos, setTodos] = React.useState(defaultTodos);
// El estado de nuestra búsqueda
const [searchValue, setSearchValue] = React.useState('');
return (
<React.Fragment>
<TodoCounter
total={totalTodos}
completed={completedTodos}
/>
<TodoSearch
searchValue={searchValue}
setSearchValue={setSearchValue}
/>
<TodoList>
{/* Regresamos solamente los TODOs buscados */}
{searchedTodos.map(todo => (
<TodoItem
key={todo.text}
text={todo.text}
completed={todo.completed}
/>
))}
</TodoList>
<CreateTodoButton />
</React.Fragment>
);
}
Para crear las funcionalidades de completar y eliminar TODOs podemos crear una función que
reciba el id o texto de nuestro TODO, para después editarlo o eliminarlo.
Completando TODOs
Creamos la función completeTodo, que recibirá el texto de nuestro TODO, ubicamos el TODO en
nuestro arreglo, cambiamos el valor de la propiedad completed de nuestro TODO y muy
importante actualizar nuestro estado, para que React pueda re-renderizar nuestros TODOs con los
nuevos datos.
Eliminando TODOs
Podemos hacer algo parecido a la función de completar, pero ahora, en lugar de cambiar si está
completada o no, solamente la eliminaremos de nuestros TODOs con el método splice, y también
regresaremos un nuevo arreglo con los TODOs actualizados.
App.js
Una vez tenemos creada la lógica para completar y eliminar TODOs, podemos pasar esas funciones
a nuestros TodoItem.
const defaultTodos = [
{ text: 'Cortar cebolla', completed: true },
{ text: 'Tomar el cursso de intro a React', completed: false },
{ text: 'Llorar con la llorona', completed: true },
{ text: 'LALALALAA', completed: false },
];
function App() {
const [todos, setTodos] = React.useState(defaultTodos);
const [searchValue, setSearchValue] = React.useState('');
if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}
return (
{searchedTodos.map(todo => (
completeTodo(todo.text)}
onDelete={() => deleteTodo(todo.text)}
/>
))}
);
}
Para que nuestra aplicación funcione también tenemos que recibir las props en nuestros ítems y
usarlas.
La organización de un proyecto es algo muy importante, nos ayuda a tener un mejor control y
orden sobre nuestra aplicación.
No existe una mejor estructura de carpetas, existen varias estructuras, y la más utilizada es la
agrupación por tipo de archivo, puedes usar la que más te guste, la que mejor se adapte a tu
proyecto, o ¡incluso crear una propia!
Agrupación por tipo de archivo
En esta estructura solo agrupamos los archivos similares, es la más recomendada para proyectos
grandes, también sirve para tener una excelente organización en proyectos pequeños, por
ejemplo:
Puedes usar la estructura que más te guste, en nuestra aplicación, ya que son muy pocos
componentes, solamente crearemos una carpeta por cada componente.
Una vez tengamos esto listo, ahora podemos importarlo dentro de nuestro archivo App.js, pero si
queremos seguir las reglas de stateful y stateless, no tiene mucho sentido, por eso vamos a
abstraer la UI de nuestro archivo App.js en otro componente que llamaremos AppUI.js:
App/index.js
const defaultTodos = [
{ text: 'Cortar cebolla', completed: true },
{ text: 'Tomar el cursso de intro a React', completed: false },
{ text: 'Llorar con la llorona', completed: true },
{ text: 'LALALALAA', completed: false },
];
function App() {
const [todos, setTodos] = React.useState(defaultTodos);
const [searchValue, setSearchValue] = React.useState('');
if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}
return (
<AppUI
totalTodos={totalTodos}
completedTodos={completedTodos}
searchValue={searchValue}
setSearchValue={setSearchValue}
searchedTodos={searchedTodos}
completeTodo={completeTodo}
deleteTodo={deleteTodo}
/>
);
}
App/AppUI.js
function AppUI({
totalTodos,
completedTodos,
searchValue,
setSearchValue,
searchedTodos,
completeTodo,
deleteTodo,
}) {
return (
<React.Fragment>
<TodoCounter
total={totalTodos}
completed={completedTodos}
/>
<TodoSearch
searchValue={searchValue}
setSearchValue={setSearchValue}
/>
<TodoList>
{searchedTodos.map(todo => (
<TodoItem
key={todo.text}
text={todo.text}
completed={todo.completed}
onComplete={() => completeTodo(todo.text)}
onDelete={() => deleteTodo(todo.text)}
/>
))}
</TodoList>
<CreateTodoButton />
</React.Fragment>
);
}
export { AppUI };
Las aplicaciones web tienen tanto front-end como back-end, en front-end se encarga de la parte
visual e interactuar con los usuarios, así como de conectarse con el back-end, en donde se maneja
la autenticación, el almacenamiento de datos en bases de datos, esta es una manera muy utilizada
para la persistencia de datos.
También es posible la persistencia de datos sin necesidad del back-end, utilizando la API de
almacenamiento web, el localStorage, que nos permite almacenar datos localmente en el
navegador, que persistirán incluso si el usuario recarga la página o cierra el navegador.
Además, existe otra forma de almacenar datos, aunque no es persistente, se llama sessionStorage,
se utiliza exactamente igual que localStorage, la diferencia es que los datos en localStorage son
persistentes.
Local Storage
Nos permite guardar datos persistentes en el navegador del usuario, que podremos acceder,
modificar y hasta eliminar, para esto localStorage tiene varios métodos.
Es muy importante saber que localStorage solamente puede guardar texto, no objetos, arreglos,
números, solo strings para esto podemos utilizar unos métodos de JSON:
Para crear la lógica de nuestro almacenamiento local, antes de acceder a nuestro ítem, debemos
tener en cuenta que nuestro usuario puede ser nuevo y no tener ningún TODO creado, en este
caso necesitaríamos crear un arreglo vacío, si el usuario ya tiene TODOs creados, deberíamos
obtener sus TODOs del localStorage.
function App() {
// Traemos nuestros TODOs almacenados
const localStorageTodos = localStorage.getItem('TODOS_V1');
let parsedTodos;
if (!localStorageTodos) {
// Si el usuario es nuevo no existe un item en localStorage, por lo tanto guardamos uno con
un array vacío
localStorage.setItem('TODOS_V1', JSON.stringify([]));
parsedTodos = [];
} else {
// Si existen TODOs en el localStorage los regresamos como nuestros todos
parsedTodos = JSON.parse(localStorageTodos);
}
if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}
return (
<AppUI
totalTodos={totalTodos}
completedTodos={completedTodos}
searchValue={searchValue}
setSearchValue={setSearchValue}
searchedTodos={searchedTodos}
completeTodo={completeTodo}
deleteTodo={deleteTodo}
/>
);
}
Todavía no creamos la lógica para agregar nuevos TODOs, pero si probamos añadiendo TODOs de
prueba en nuestro localStorage, ya debería funcionar todo bien, o al menos a simple vista. Cuando
interactuamos con nuestra aplicación, completando o eliminando TODOs todavía no se ve
reflejado en nuestro localStorage.
Para lograr esto, crearemos una función puente, entre nuestra función que actualiza nuestro
estado para actualizar nuestro localStorage.
Todavía no creamos la lógica para agregar nuevos TODOs, pero si probamos añadiendo TODOs de
prueba en nuestro localStorage, ya debería funcionar todo bien, o al menos a simple vista. Cuando
interactuamos con nuestra aplicación, completando o eliminando TODOs todavía no se ve
reflejado en nuestro localStorage.
Para lograr esto, crearemos una función puente, entre nuestra función que actualiza nuestro
estado para actualizar nuestro localStorage.
import React from 'react';
import { AppUI } from './AppUI';
function App() {
const localStorageTodos = localStorage.getItem('TODOS_V1');
let parsedTodos;
if (!localStorageTodos) {
localStorage.setItem('TODOS_V1', JSON.stringify([]));
parsedTodos = [];
} else {
parsedTodos = JSON.parse(localStorageTodos);
}
if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}
return (
<AppUI
totalTodos={totalTodos}
completedTodos={completedTodos}
searchValue={searchValue}
setSearchValue={setSearchValue}
searchedTodos={searchedTodos}
completeTodo={completeTodo}
deleteTodo={deleteTodo}
/>
);
}
Pero ahora tenemos demasiado código en nuestro archivo App.js eso no es bueno, debemos de
intentar separar la lógica para que sea más fácil de leer y esté mejor organizado.
Algo súper interesante de React es que podemos crear hooks personalizados para ejecutar
procesos para manejar información sin que afecte a otros componentes, lo que haremos será
abstraer nuestra lógica de localStorage para manejarla dentro de nuestro propio hook.
1. Nuestro hook personalizado debe empezar por use, por ejemplo: usePatito, useTodos o
useUnicornio.
2. No anidar hooks en loops u otros bloques.
3. Llamar dentro de componentes de React o hooks propios, nunca dentro de funciones
normales.
Creando nuestro Custom Hook
El objetivo de un custom hook es reutilizar código, entonces este hook debería poder funcionar
para guardar cualquier tipo de dato en el localStorage.
Primero necesitamos analizar que parámetros necesita tener nuestro custom hook:
También tenemos que regresar algunos datos para que nuestro hook sea funcional:
¡Ahora que sabemos qué tenemos que hacer, podemos empezar a crear nuestro custom hook!
Ahora que hemos creado nuestro custom hook podemos usarlo las veces que queramos.
// const defaultTodos = [
// { text: 'Cortar cebolla', completed: true },
// { text: 'Tomar el cursso de intro a React', completed: false },
// { text: 'Llorar con la llorona', completed: true },
// { text: 'LALALALAA', completed: false },
// ];
if (!localStorageItem) {
localStorage.setItem(itemName, JSON.stringify(initialValue));
parsedItem = initialValue;
} else {
parsedItem = JSON.parse(localStorageItem);
}
return [
item,
saveItem,
];
}
function App() {
// Desestructuramos los datos que retornamos de nuestro custom hook, y le pasamos los
argumentos que necesitamos (nombre y estado inicial)
const [todos, saveTodos] = useLocalStorage('TODOS_V1', []);
const [searchValue, setSearchValue] = React.useState('');
if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}
return (
<AppUI
totalTodos={totalTodos}
completedTodos={completedTodos}
searchValue={searchValue}
setSearchValue={setSearchValue}
searchedTodos={searchedTodos}
completeTodo={completeTodo}
deleteTodo={deleteTodo}
/>,
);
}
Algo súper interesante de React es que podemos crear hooks personalizados para ejecutar
procesos para manejar información sin que afecte a otros componentes, lo que haremos será
abstraer nuestra lógica de localStorage para manejarla dentro de nuestro propio hook.
1. Nuestro hook personalizado debe empezar por use, por ejemplo: usePatito, useTodos o
useUnicornio.
2. No anidar hooks en loops u otros bloques.
3. Llamar dentro de componentes de React o hooks propios, nunca dentro de funciones
normales.
Creando nuestro Custom Hook
El objetivo de un custom hook es reutilizar código, entonces este hook debería poder funcionar
para guardar cualquier tipo de dato en el localStorage.
Primero necesitamos analizar que parámetros necesita tener nuestro custom hook:
También tenemos que regresar algunos datos para que nuestro hook sea funcional:
¡Ahora que sabemos qué tenemos que hacer, podemos empezar a crear nuestro custom hook!
Ahora que hemos creado nuestro custom hook podemos usarlo las veces que queramos.
// const defaultTodos = [
// { text: 'Cortar cebolla', completed: true },
// { text: 'Tomar el cursso de intro a React', completed: false },
// { text: 'Llorar con la llorona', completed: true },
// { text: 'LALALALAA', completed: false },
// ];
if (!localStorageItem) {
localStorage.setItem(itemName, JSON.stringify(initialValue));
parsedItem = initialValue;
} else {
parsedItem = JSON.parse(localStorageItem);
}
return [
item,
saveItem,
];
}
function App() {
// Desestructuramos los datos que retornamos de nuestro custom hook, y le pasamos los
argumentos que necesitamos (nombre y estado inicial)
const [todos, saveTodos] = useLocalStorage('TODOS_V1', []);
const [searchValue, setSearchValue] = React.useState('');
if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}
return (
<AppUI
totalTodos={totalTodos}
completedTodos={completedTodos}
searchValue={searchValue}
setSearchValue={setSearchValue}
searchedTodos={searchedTodos}
completeTodo={completeTodo}
deleteTodo={deleteTodo}
/>,
);
}
Ahora nuestro código está mucho mejor organizado, si queremos tener aún más control de
nuestro proyecto, incluso podemos crear una carpeta para hooks, y luego poder importarlos a
cualquier parte de nuestro proyecto.
El hook de efecto en React nos permite ejecutar un pedazo de código cada vez que necesitemos, a
lo largo de la vida de nuestro componente, también cuando se cumplan ciertas condiciones.
Algo curioso e importante de saber es que el código dentro de nuestro hook de efecto no se
ejecuta como el resto del código, se ejecutará inicialmente justo antes de hacer el renderizado del
HTML que resulte de nuestro código de React.
1. Función que se ejecutará en cada fase del ciclo de vida de nuestro componente.
2. Las condiciones de cuándo debe ejecutarse esta función dentro de un arreglo, cada que se
actualice cualquier dato que le pasemos a este arreglo, se volverá a ejecutar nuestra
función.
Existen tres diferentes maneras para aplicar el hook de efecto, todas funcionan diferente a la
hora de re-renderizar nuestros componentes.
Cuando no le pasamos un segundo argumento con las condiciones de cuándo se debe re-
ejecutar nuestra función, React tomará como condiciones que se debe ejecutar nuestra
función todas las veces que ocurra un re-renderizado, y también cada vez que se actualice
alguna prop (Si es que el componente recibe alguna).
Cuando pasamos un arreglo vacío, le estás diciendo a React que no hay ninguna condición
para volver a ejecutar el código de nuestra función, entonces solamente se ejecutará en el
renderizado inicial.
3. Pasando un arreglo con datos: useEffect(funcion, [val1, val2, valN])
Cuando especificas las condiciones dentro del arreglo del segundo parámetro, le estás
diciendo a React que ejecute nuestra función en el renderizado inicial y también cuando
algún dato del arreglo cambie.
Dentro de una aplicación web, al trabajar con APIs, existen muchos factores para determinar
cuánto tardará en cargar nuestra aplicación, como la velocidad de nuestro internet, la
distancia del servidor, etc.
Al trabajar con APIs también debemos tener en cuenta que puede tardar en cargar mucho
nuestra aplicación, o incluso puede ocurrir algún error, todo esto lo debemos de manejar para
mantener a nuestro usuario informado.
El hook de efecto nos permite saber cuándo ya renderizó nuestra aplicación, así podemos
mostrar un mensaje de cargando o alguna animación en lo que se completa la petición,
también con JavaScript podemos manejar los errores con try y catch, y haciendo uso del hook
de estado podemos guardar si está cargando o hubo algún error.
App/index.js
React.useEffect(() => {
// Simulamos un segundo de delay de carga
setTimeout(() => {
// Manejamos la tarea dentro de un try/catch por si ocurre algún error
try {
const localStorageItem = localStorage.getItem(itemName);
let parsedItem;
if (!localStorageItem) {
localStorage.setItem(itemName, JSON.stringify(initialValue));
parsedItem = initialValue;
} else {
parsedItem = JSON.parse(localStorageItem);
}
setItem(parsedItem);
} catch(error) {
// En caso de un error lo guardamos en el estado
setError(error);
} finally {
// También podemos utilizar la última parte del try/cath (finally) para terminar la carga
setLoading(false);
}
}, 1000);
});
// Para tener un mejor control de los datos retornados, podemos regresarlos dentro de un
objeto
return {
item,
saveItem,
loading,
error,
};
}
function App() {
// Desestructuramos los nuevos datos de nustro custom hook
const {
item: todos,
saveItem: saveTodos,
loading,
error,
} = useLocalStorage('TODOS_V1', []);
const [searchValue, setSearchValue] = React.useState('');
if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}
return (
{/* Pasamos los valores de loading y error */}
<AppUI
loading={loading}
error={error}
totalTodos={totalTodos}
completedTodos={completedTodos}
searchValue={searchValue}
setSearchValue={setSearchValue}
searchedTodos={searchedTodos}
completeTodo={completeTodo}
deleteTodo={deleteTodo}
/>
);
}
<TodoList>
// Mostramos un mensaje en caso de que ocurra algún error
{error && <p>Desespérate, hubo un error...</p>}
// Mostramos un mensaje de cargando, cuando la aplicación está cargando lo sdatos
{loading && <p>Estamos cargando, no desesperes...</p>}
// Si terminó de cargar y no existen TODOs, se muestra un mensaje para crear el primer
TODO
{(!loading && !searchedTodos.length) && <p>¡Crea tu primer TODO!</p>}
{searchedTodos.map(todo => (
<TodoItem
key={todo.text}
text={todo.text}
completed={todo.completed}
onComplete={() => completeTodo(todo.text)}
onDelete={() => deleteTodo(todo.text)}
/>
))}
</TodoList>
<CreateTodoButton />
</React.Fragment>
);
}
export { AppUI };