Skip to content

Commit

Permalink
New simplified version
Browse files Browse the repository at this point in the history
  • Loading branch information
fdodino committed Nov 9, 2023
1 parent 33edce7 commit 10534b8
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 72 deletions.
67 changes: 6 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,70 +37,15 @@ export const MailReader = () => {
const [mails, setMails] = useState([])
```
### useEffect
## Buscando mails
Para poder hacer la llamada asincrónica al backend (simulado), utilizaremos el hook `useEffect` que reemplaza a los eventos `componentDidMount`, `componentDidUpdate` y `componentWillUnmount`.
Para buscar mails utilizamos una función asincrónica que recibe el nuevo texto de búsqueda y
```js
useEffect(() => {
const fetchMails = async () => {
const mails = await mailService.getMails(textoBusqueda)
setMails(mails)
}

fetchMails()
}, [textoBusqueda])
```
El hook [`useEffect`](https://es.reactjs.org/docs/hooks-effect.html) sirve para ejecutar código una vez que React actualizó el DOM (después de que se evaluó la función `render`). La idea de **efecto** es importante, porque hablamos de él cuando se modifica el estado de un objeto, o cuando se modifica el valor de una referencia.
En el ejemplo de arriba vemos cómo
- se define una función asincrónica que le pide los mails al backend
- luego se invoca a dicha función
- y se registra como árbol de dependencias el estado `textoBusqueda`. Esto significa que el useEffect se ejecutará la primera vez, y cada vez que cambie el estado del texto de búsqueda.
El ciclo de vida entonces es:
- render() inicial
- useEffect() inicial, que dispara una actualización al backend
- cuando completa, se actualiza el estado de la página, concretamente los mails
- como el estado cambió, nuevamente se dispara el render, mostrando la lista de mails
- el estado que cambió fue `mails`, que no está asociado a ningún `useEffect` definido. Es importante no registrar en el árbol de dependencias la referencia al estado `mails`, porque dado que estamos haciendo un `setMails(mails)`, esto podría generar un loop infinito (render inicial > useEffect inicial que setea los mails > render por cambio de estado de mails > useEffect que se activa ante un cambio en los mails que vuelve a disparar la búsqueda y dispara un cambio de estado por los mails > render por cambio de estado de mails ...)
## Buenas prácticas y gotchas del useEffect
- evitar el loop infinito no incluyendo en el árbol de dependencias los mismos estados que estamos afectando dentro de la función
- el useEffect puede llamar a una función asincrónica pero no debe devolver una función asincrónica porque no se ejecutará:
```js
// no hacer esto
useEffect(() => {
return async () => {
const mails = await mailService.getMails(textoBusqueda)
setMails(mails)
}
}, ...
```
- el `useEffect` es útil para tomar y liberar recursos (como el equivalente del par `componentDidMount` y `componentWillUnmount`), aunque puede ser bastante más confuso entender cómo resolverlo
En general el hook `useEffect` es una implementación poco intuitiva, muy fácil de romper y nuestra apuesta es que será mejorada con el correr del tiempo. Por el momento la alternativa es contar con [reglas del linter](https://es.reactjs.org/docs/hooks-rules.html) que reemplaza el diseño un tanto confuso que hoy nos ofrece para seguir manteniendo nuestros componentes funcionales.
- actualiza el texto de búsqueda (para que se vea en el input)
- dispara la búsqueda de mails
- y ubica el resultado de esa búsqueda en el estado para actualizar la lista de mails
Cuando el usuario escribe algo en el input de búsqueda, eso dispara la actualización del estado:
```js
<InputText placeholder="texto a buscar" value={textoBusqueda} onChange={(event) => setTextoBusqueda(event.target.value)} />
```
que a su vez es referenciado por el useEffect, por lo que dispara nuevamente la consulta al service.
Cada vez que hay nuevos mails, los componentes hijos reciben como props dicha información:
```js
<MailsSummary mails={mails} />
<MailsGrid mails={mails} ... />
```
En la nueva variante **no usamos el hook useEffect** y como conclusión es bastante simple de entender. Si querés ver cómo es la implementación con useEffect podés leer [esta página](./useEffect.md).
## MailsSummary
Expand Down
29 changes: 18 additions & 11 deletions src/components/MailReader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,37 @@ export const MailReader = () => {
await mailService.actualizar(mail)
setMails([...mails])

// otra variante de un amigo: Dami Pereira
// setMails(mails.map((mail) =>
// mail.id === idLeido ? mail.leer() : mail
// ))
// leer debería ser una función que devuelve un nuevo mail
}

useEffect(() => {
const fetchMails = async () => {
const mails = await mailService.getMails(textoBusqueda)
setMails(mails)
}
// Variante más simple
const buscarMails = async (textoBusquedaNuevo) => {
setTextoBusqueda(textoBusquedaNuevo)
const nuevosMails = await mailService.getMails(textoBusquedaNuevo)
setMails(nuevosMails)
}

// otra variante más rebuscada
// es que el InputText tenga el onchange definido como onChange={(event) => setTextoBusqueda(event.target.value)}
//
// useEffect(() => {
// const fetchMails = async () => {
// const mails = await mailService.getMails(textoBusqueda)
// setMails(mails)
// }

// invocamos a la función que obtiene los mails
// en base al criterio de búsqueda
fetchMails()
}, [textoBusqueda])
// fetchMails()
// }, [textoBusqueda])

return (
<div>
<Panel header="Mails">
<div className="p-col-12 p-md-4">
<div className="p-inputgroup">
<InputText placeholder="texto a buscar" data-testid='textSearch' value={textoBusqueda} onChange={(event) => setTextoBusqueda(event.target.value)} />
<InputText placeholder="texto a buscar" data-testid='textSearch' value={textoBusqueda} onChange={(event) => buscarMails(event.target.value)} />
<Button icon="pi pi-search" />
</div>
</div>
Expand Down
73 changes: 73 additions & 0 deletions useEffect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

## Resolución de la búsqueda mediante el hook useEffect

Para poder hacer la llamada asincrónica al backend (simulado), utilizaremos el hook `useEffect` que reemplaza a los eventos `componentDidMount`, `componentDidUpdate` y `componentWillUnmount`.

```js
useEffect(() => {
const fetchMails = async () => {
const mails = await mailService.getMails(textoBusqueda)
setMails(mails)
}

fetchMails()
}, [textoBusqueda])
```

El hook [`useEffect`](https://es.reactjs.org/docs/hooks-effect.html) sirve para ejecutar código una vez que React actualizó el DOM (después de que se evaluó la función `render`). La idea de **efecto** es importante, porque hablamos de él cuando se modifica el estado de un objeto, o cuando se modifica el valor de una referencia.

En el ejemplo de arriba vemos cómo

- se define una función asincrónica que le pide los mails al backend
- luego se invoca a dicha función
- y se registra como árbol de dependencias el estado `textoBusqueda`. Esto significa que el useEffect se ejecutará la primera vez, y cada vez que cambie el estado del texto de búsqueda.

El ciclo de vida entonces es:

- render() inicial
- useEffect() inicial, que dispara una actualización al backend
- cuando completa, se actualiza el estado de la página, concretamente los mails
- como el estado cambió, nuevamente se dispara el render, mostrando la lista de mails
- el estado que cambió fue `mails`, que no está asociado a ningún `useEffect` definido. Es importante no registrar en el árbol de dependencias la referencia al estado `mails`, porque dado que estamos haciendo un `setMails(mails)`, esto podría generar un loop infinito (render inicial > useEffect inicial que setea los mails > render por cambio de estado de mails > useEffect que se activa ante un cambio en los mails que vuelve a disparar la búsqueda y dispara un cambio de estado por los mails > render por cambio de estado de mails ...)

## Buenas prácticas y gotchas del useEffect

- evitar el loop infinito no incluyendo en el árbol de dependencias los mismos estados que estamos afectando dentro de la función
- el useEffect puede llamar a una función asincrónica pero no debe devolver una función asincrónica porque no se ejecutará:

```js
// no hacer esto
useEffect(() => {
return async () => {
const mails = await mailService.getMails(textoBusqueda)
setMails(mails)
}
}, ...
```
- el `useEffect` es útil para tomar y liberar recursos (como el equivalente del par `componentDidMount` y `componentWillUnmount`), aunque puede ser bastante más confuso entender cómo resolverlo
En general el hook `useEffect` es una implementación poco intuitiva, muy fácil de romper y nuestra apuesta es que será mejorada con el correr del tiempo. Por el momento la alternativa es contar con [reglas del linter](https://es.reactjs.org/docs/hooks-rules.html) que reemplaza el diseño un tanto confuso que hoy nos ofrece para seguir manteniendo nuestros componentes funcionales.
Cuando el usuario escribe algo en el input de búsqueda, eso dispara la actualización del estado:
```js
<InputText placeholder="texto a buscar" value={textoBusqueda} onChange={(event) => setTextoBusqueda(event.target.value)} />
```
que a su vez es referenciado por el useEffect, por lo que dispara nuevamente la consulta al service.
Cada vez que hay nuevos mails, los componentes hijos reciben como props dicha información:
```js
<MailsSummary mails={mails} />
<MailsGrid mails={mails} ... />
```
## Por qué no utilizar el hook useEffect
- es una resolución que requiere entender cómo funciona el motor de React (se pierde la declaratividad)...
- además de ser complejo: actualizamos el estado del texto a buscar para luego asociarlo a un useEffect que a su vez termina actualizando el estado de los mails
- la variante que hace el fetch resuelve todo a un único punto donde se resuelve todo
- y el useEffect además es una función que sirve más para definir _custom hooks_ más que componentes propios de React

0 comments on commit 10534b8

Please sign in to comment.