0% encontró este documento útil (0 votos)
6 vistas33 páginas

React Manejo Eventos

El documento describe el manejo de eventos y el estado en React, destacando diferencias con el DOM y el uso de hooks para componentes funcionales. También se aborda la organización de archivos, la persistencia de datos con localStorage y la implementación de funcionalidades para contar, buscar, completar y eliminar TODOs en una aplicación. Se enfatiza la importancia de una buena estructura de carpetas y el manejo adecuado del estado para mejorar la experiencia del usuario.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
6 vistas33 páginas

React Manejo Eventos

El documento describe el manejo de eventos y el estado en React, destacando diferencias con el DOM y el uso de hooks para componentes funcionales. También se aborda la organización de archivos, la persistencia de datos con localStorage y la implementación de funcionalidades para contar, buscar, completar y eliminar TODOs en una aplicación. Se enfatiza la importancia de una buena estructura de carpetas y el manejo adecuado del estado para mejorar la experiencia del usuario.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 33

Manejo de 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:

1. En React los eventos son nombrados usando camelCase.


2. Tenemos que pasar una función, ya sea en línea o almacenada en una variable.
3. No puedes regresar false para prevenir el comportamiento por defecto, debes utilizar
preventDefault explícitamente.
Pasando argumentos a escuchadores de eventos

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.

Si necesitamos pasar argumentos a nuestras funciones, necesitamos encerrar nuestra función


dentro de otra función, esto porque al pasarle argumentos a una función la estamos ejecutando,
veamos un ejemplo:

 Es importante siempre pasar una función.

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).

¿Qué son los Hooks?

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.

Estado en componentes clase

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.

Podemos importar este hook directamente de React o desestructurándolo de React:

Una vez lo importamos ya podemos usarlo en nuestro componente, este hook nos regresará un
array con dos elementos:

1. El valor de nuestro estado (Getter).


2. La función que se ocupa de actualizar nuestro estado (Setter).

También podemos pasarle un valor inicial a nuestro estado dentro de los paréntesis.

Por ejemplo, en el buscador de nuestra aplicación,


Tipos de componentes

Stateful: son componentes que tienen declaración de estado en su función.

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.

import React from 'react';


import { TodoCounter } from './TodoCounter';
import { TodoSearch } from './TodoSearch';
import { TodoList } from './TodoList';
import { TodoItem } from './TodoItem';
import { CreateTodoButton } from './CreateTodoButton';
// import './App.css';

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>
);
}

export default App;

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.

Primero crearemos nuestro estado de búsqueda en App.js, y utilizaremos el método filter de


JavaScript para filtrar los TODOs que coincidan con nuestra búsqueda, y también haremos uso de
toLowerCase(), para poder filtrar sin importar si las letras son mayúsculas o minúsculas.

import React from 'react';


import { TodoCounter } from './TodoCounter';
import { TodoSearch } from './TodoSearch';
import { TodoList } from './TodoList';
import { TodoItem } from './TodoItem';
import { CreateTodoButton } from './CreateTodoButton';
// import './App.css';

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('');

const completedTodos = todos.filter(todo => !!todo.completed).length;


const totalTodos = todos.length;
// Creamos una nueva variable en donde guardaremos las coincidencias con la búsqueda
let searchedTodos = [];

// Lógica para filtrar


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 (
<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>
);
}

export default App;

Completando y eliminando TODOs

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.

import React from 'react';


import { TodoCounter } from './TodoCounter';
import { TodoSearch } from './TodoSearch';
import { TodoList } from './TodoList';
import { TodoItem } from './TodoItem';
import { CreateTodoButton } from './CreateTodoButton';
// import './App.css';

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('');

const completedTodos = todos.filter(todo => !!todo.completed).length;


const totalTodos = todos.length;

let searchedTodos = [];

if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}

const completeTodo = (text) => {


const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos[todoIndex].completed = true;
setTodos(newTodos);
};

const deleteTodo = (text) => {


const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos.splice(todoIndex, 1);
setTodos(newTodos);
};

return (

{searchedTodos.map(todo => (
completeTodo(todo.text)}
onDelete={() => deleteTodo(todo.text)}
/>
))}

);
}

export default App;


TodoItem.js

Para que nuestra aplicación funcione también tenemos que recibir las props en nuestros ítems y
usarlas.

Organización de archivos y carpetas

La organización de un proyecto es algo muy importante, nos ayuda a tener un mejor control y
orden sobre nuestra aplicación.

¿Cuál es la mejor estructura?

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:

Creando la estructura de nuestro proyecto

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

import React from 'react';


import { AppUI } from './AppUI';

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('');

const completedTodos = todos.filter(todo => !!todo.completed).length;


const totalTodos = todos.length;

let searchedTodos = [];

if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}

const completeTodo = (text) => {


const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos[todoIndex].completed = true;
setTodos(newTodos);
};

const deleteTodo = (text) => {


const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos.splice(todoIndex, 1);
setTodos(newTodos);
};

return (
<AppUI
totalTodos={totalTodos}
completedTodos={completedTodos}
searchValue={searchValue}
setSearchValue={setSearchValue}
searchedTodos={searchedTodos}
completeTodo={completeTodo}
deleteTodo={deleteTodo}
/>
);
}

export default App;

App/AppUI.js

import React from 'react';


import { TodoCounter } from '../TodoCounter';
import { TodoSearch } from '../TodoSearch';
import { TodoList } from '../TodoList';
import { TodoItem } from '../TodoItem';
import { CreateTodoButton } from '../CreateTodoButton';

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 };

Persistencia de datos con Local Storage

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.

 Guardar datos: setItem(nombre, dato)


 Acceder a datos: getItem(nombre)
 Borrar un dato: removeItem(nombre)
 Eliminar todos los datos: clear(nombre)

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:

 Convertir a texto: JSON.stringify()


 Convertir a JavaScript: JSON.parse()
Local Storage en ToDo App

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.

import React from 'react';


import { AppUI } from './AppUI';

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);
}

// Guardamos nuestros TODOs del localStorage en nuestro estado


const [todos, setTodos] = React.useState(parsedTodos);
const [searchValue, setSearchValue] = React.useState('');

const completedTodos = todos.filter(todo => !!todo.completed).length;


const totalTodos = todos.length;

let searchedTodos = [];

if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}

const completeTodo = (text) => {


const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos[todoIndex].completed = true;
setTodos(newTodos);
};

const deleteTodo = (text) => {


const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos.splice(todoIndex, 1);
setTodos(newTodos);
};

return (
<AppUI
totalTodos={totalTodos}
completedTodos={completedTodos}
searchValue={searchValue}
setSearchValue={setSearchValue}
searchedTodos={searchedTodos}
completeTodo={completeTodo}
deleteTodo={deleteTodo}
/>
);
}

export default App;

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);
}

const [todos, setTodos] = React.useState(parsedTodos);


const [searchValue, setSearchValue] = React.useState('');

const completedTodos = todos.filter(todo => !!todo.completed).length;


const totalTodos = todos.length;

let searchedTodos = [];

if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}

// Creamos la función en la que actualizaremos nuestro localStorage


const saveTodos = (newTodos) => {
// Convertimos a string nuestros TODOs
const stringifiedTodos = JSON.stringify(newTodos);
// Los guardamos en el localStorage
localStorage.setItem('TODOS_V1', stringifiedTodos);
// Actualizamos nuestro estado
setTodos(newTodos);
};

const completeTodo = (text) => {


const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos[todoIndex].completed = true;
// Cada que el usuario interactúe con nuestra aplicación se guardarán los TODOs con nuestra
nueva función
saveTodos(newTodos);
};

const deleteTodo = (text) => {


const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos.splice(todoIndex, 1);
// Cada que el usuario interactúe con nuestra aplicación se guardarán los TODOs con nuestra
nueva función
saveTodos(newTodos);
};

return (
<AppUI
totalTodos={totalTodos}
completedTodos={completedTodos}
searchValue={searchValue}
setSearchValue={setSearchValue}
searchedTodos={searchedTodos}
completeTodo={completeTodo}
deleteTodo={deleteTodo}
/>
);
}

export default App;

No fue tan complicado, ¿Verdad?

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.

Custom Hook para Local Storage

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.

Reglas para crear un custom 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:

1. Un nombre para el item en nuestro localStorage.


2. Un estado inicial

También tenemos que regresar algunos datos para que nuestro hook sea funcional:

1. Los datos actuales de nuestro ítem en el localStorage.


2. Una función para actualizar los datos de este ítem.

¡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.

¡Vamos a añadirlo a la lógica de nuestra aplicación!

import React from 'react';


import { AppUI } from './AppUI';

// 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 useLocalStorage(itemName, initialValue) {


const localStorageItem = localStorage.getItem(itemName);
let parsedItem;

if (!localStorageItem) {
localStorage.setItem(itemName, JSON.stringify(initialValue));
parsedItem = initialValue;
} else {
parsedItem = JSON.parse(localStorageItem);
}

const [item, setItem] = React.useState(parsedItem);

const saveItem = (newItem) => {


const stringifiedItem = JSON.stringify(newItem);
localStorage.setItem(itemName, stringifiedItem);
setItem(newItem);
};

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('');

const completedTodos = todos.filter(todo => !!todo.completed).length;


const totalTodos = todos.length;
let searchedTodos = [];

if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}

const completeTodo = (text) => {


const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos[todoIndex].completed = true;
saveTodos(newTodos);
};

const deleteTodo = (text) => {


const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos.splice(todoIndex, 1);
saveTodos(newTodos);
};

return (
<AppUI
totalTodos={totalTodos}
completedTodos={completedTodos}
searchValue={searchValue}
setSearchValue={setSearchValue}
searchedTodos={searchedTodos}
completeTodo={completeTodo}
deleteTodo={deleteTodo}
/>,
);
}

export default App;


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.

¡Te retamos a que lo hagas!


Custom Hook para Local Storage

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.

Reglas para crear un custom 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:

1. Un nombre para el item en nuestro localStorage.


2. Un estado inicial

También tenemos que regresar algunos datos para que nuestro hook sea funcional:

1. Los datos actuales de nuestro ítem en el localStorage.


2. Una función para actualizar los datos de este ítem.

¡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.

¡Vamos a añadirlo a la lógica de nuestra aplicación!


import React from 'react';
import { AppUI } from './AppUI';

// 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 useLocalStorage(itemName, initialValue) {


const localStorageItem = localStorage.getItem(itemName);
let parsedItem;

if (!localStorageItem) {
localStorage.setItem(itemName, JSON.stringify(initialValue));
parsedItem = initialValue;
} else {
parsedItem = JSON.parse(localStorageItem);
}

const [item, setItem] = React.useState(parsedItem);

const saveItem = (newItem) => {


const stringifiedItem = JSON.stringify(newItem);
localStorage.setItem(itemName, stringifiedItem);
setItem(newItem);
};

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('');

const completedTodos = todos.filter(todo => !!todo.completed).length;


const totalTodos = todos.length;

let searchedTodos = [];

if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}

const completeTodo = (text) => {


const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos[todoIndex].completed = true;
saveTodos(newTodos);
};

const deleteTodo = (text) => {


const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos.splice(todoIndex, 1);
saveTodos(newTodos);
};

return (
<AppUI
totalTodos={totalTodos}
completedTodos={completedTodos}
searchValue={searchValue}
setSearchValue={setSearchValue}
searchedTodos={searchedTodos}
completeTodo={completeTodo}
deleteTodo={deleteTodo}
/>,
);
}

export default App;

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.

¡Te retamos a que lo hagas!


Manejo de efectos

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.

Condiciones para nuestro hook de efecto

El hook de React, useEffect, puede recibir dos argumentos:

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.

Diferentes maneras de actualizar nuestros componentes

Existen tres diferentes maneras para aplicar el hook de efecto, todas funcionan diferente a la
hora de re-renderizar nuestros componentes.

1. Sin pasar un arreglo como segundo argumento: useEffect(funcion)

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).

2. Pasando un arreglo vacío: useEffect(funcion, [])

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.

Simulando una petición a una API

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

import React from 'react';


import { AppUI } from './AppUI';

function useLocalStorage(itemName, initialValue) {


// Creamos el estado inicial para nuestros errores y carga
const [error, setError] = React.useState(false);
const [loading, setLoading] = React.useState(true);
const [item, setItem] = React.useState(initialValue);

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);
});

const saveItem = (newItem) => {


// Manejamos la tarea dentro de un try/catch por si ocurre algún error
try {
const stringifiedItem = JSON.stringify(newItem);
localStorage.setItem(itemName, stringifiedItem);
setItem(newItem);
} catch(error) {
// En caso de algún error lo guardamos en el estado
setError(error);
}
};

// 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('');

const completedTodos = todos.filter(todo => !!todo.completed).length;


const totalTodos = todos.length;

let searchedTodos = [];

if (!searchValue.length >= 1) {
searchedTodos = todos;
} else {
searchedTodos = todos.filter(todo => {
const todoText = todo.text.toLowerCase();
const searchText = searchValue.toLowerCase();
return todoText.includes(searchText);
});
}

const completeTodo = (text) => {


const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos[todoIndex].completed = true;
saveTodos(newTodos);
};

const deleteTodo = (text) => {


const todoIndex = todos.findIndex(todo => todo.text === text);
const newTodos = [...todos];
newTodos.splice(todoIndex, 1);
saveTodos(newTodos);
};

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}
/>
);
}

export default App;


Una vez sabemos exactamente cuándo una aplicación está cargando y cuándo ha ocurrido
algún error, podemos usar esta información para mostrar algo al usuario.
App/AppUI.js

import React from 'react';


import { TodoCounter } from '../TodoCounter';
import { TodoSearch } from '../TodoSearch';
import { TodoList } from '../TodoList';
import { TodoItem } from '../TodoItem';
import { CreateTodoButton } from '../CreateTodoButton';

// Desescructuramos las nuesvas props


function AppUI({
loading,
error,
totalTodos,
completedTodos,
searchValue,
setSearchValue,
searchedTodos,
completeTodo,
deleteTodo,
}) {
return (
<React.Fragment>
<TodoCounter
total={totalTodos}
completed={completedTodos}
/>
<TodoSearch
searchValue={searchValue}
setSearchValue={setSearchValue}
/>

<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 };

También podría gustarte