DEV Community

Cover image for ¿Dónde ponemos el useEffect?
Kz
Kz

Posted on

¿Dónde ponemos el useEffect?

Viendo el nuevo paradigma que el equipo de React Router propone en su más reciente versión en cuanto al inicio de flujo de datos desde ese render inicial, y al ponerlo de frente con el modelo de Remix (mismo equipo), y de TanStack Router... creo que finalmente, podemos decir, de forma segura, que debemos re-pensar la función de useEffect en nuestros proyectos.

No me atrevería a decir lo mismo que diría David Kourshid en aquel video que escandalizó a todo el mundo, y que lo puso en el ojo de un huracán de críticas, pero creo que entiendo lo que quiso decir en su video "Goodbye useEffect", y es que ya no necesitaremos el hook controlador de efectos para dar inicio al flujo de datos en ese primer render de nuestros componentes.

En otras palabras, y para que esté más claro... algo así:
useEffect(() => { fetchData() }, [])
será algo del pasado.

Entonces, sí esa ya no será la implementación, ¿cuál será?.
Esa pregunta es precisamente la que quiero responder. Sin embargo, hay una más evidente aún a la que me quiero dirigir y es: "¿Qué hay de malo en el useEffect?" eso asumiendo que haya algo malo.

Veamos un escenario típico, y tratemos de entender lo que está sucediendo.

Image description

Los console.logs nos ayudan a entender en qué momento se realizan nuestras funciones. Primero se muestra el log que está por fuera del effect. Luego, se muestra el log que está dentro del effect.
Teniendo en cuenta que este código es Client Side Rendering (CSR), el log por fuera del effect se muestra tan pronto se inyecta el JavaScript al html en nuestro browser, montando así nuestro componente; tan solo entonces es que la interactividad de nuestro componente empieza a realizarse loggeando el mensaje dentro del effect, llevando a cabo una función asíncrona, que a su vez actualiza un state: data (y ya sabemos lo que pasa con el componente cada vez que su state se actualiza)... re-renderiza el componente una vez más, esta vez con su estado actualizado.

En pocas palabras, y usando nuestros logs como ejemplo, para cuando el componente esté resuelto, hemos terminado con 4 logs en consola sin contar los del strictMode, cada par representa las veces que el mismo nodo se pintó en el DOM.

Creo que esto, hace muy evidente qué es lo que se está tratando de evitar, pero sigamos un poco más.

Aquí estamos ante un coyuntura de la esencia misma de React. El comportamiento básico de un componente es que si su estado se actualiza, este generará un nuevo render. No obstante, estamos de acuerdo en que hasta esta instancia, solo estamos montando el componente e iterando sus datos y no ha existido interacción de parte del usuario, por ende preferiríamos que el componente nos cueste únicamente ese render inicial con los datos que queremos resolver.
Esto no es "intrínsecamente" posible en React por dos causas que mencionamos anteriormente:

  1. El código JS es CSR.
  2. State Updates generan re-renders.

¿Cómo lo solucionamos?

La divergencia del pensamiento de desarrollo nos ha llevado a desear componentes stateful que desde su render inicial carguen los datos que le hayamos asignado, como si parte de la resolución de ese Javascript en el proceso de hydration ya contenga los datos con los que debe crear el Nodo.
Seguramente, están pensando en SSR, pero estamos hablando de REACT, y esa se lo dejamos a NEXT Js.

El primero en rodar la pelota fue TanStack con su librería de React Query, que reduce significativamente el Boilerplate para fetching de datos en componentes, y para el manejo de efectos secundarios asíncronos a partir de eventos de usuario.
Un ejemplo del código anterior con React Query:

Image description

Sin embargo, seguimos con el mismo resultado en consola.
¿Qué hemos logrado entonces por medio de React Query?

Bueno, voy a listar dos beneficios que considero cuenta por 100:

  1. Provee manejo de datos desde un estado (STATE) del servidor. Eso significa que no tenemos que preocuparnos por englobarlo todo dentro de un estado global en nuestra app.

  2. Elimina el boilerplate para manejar funciones asíncronas, requests a api's que tienen que ser almacenadas en estados globales y posteriormente consumidas en diferentes partes. Estoy hablando textualmente de Redux Thunk, Tooltip, Saga... y demás. Cuando hablo de reducir el boilerplate, no solo estoy hablando del store, de los reducers, de los actions, de los types, de los estados del requests en los que tenemos que especificar qué comportamiento queremos que nuestra app tenga... Hablo también de todas las funciones que con las habilitaremos la interacción y mutación de los datos que provienen del servidor.

Ahora bien, retomo la frase introductoria del primer punto: "Provee manejo de datos desde un estado (STATE) del servidor" puede que nos haga plantearnos la pregunta: ¿Si el estado ya está provisto desde el servidor con el manejo de datos, y asincronismo, sería posible que el componente contenga los datos resueltos en el momento exacto en el que se pinta en el DOM?

El approach de Next es el método getStaticProps y getServerSideProps con el que se habilitan datos estáticos que se cargan con el componente una vez este existe en el DOM. Éste posteriormente se hidrata para hacerse interactivo del lado del cliente.
Next, en su última versión, anunció el uso de React como librería para la creación de Server Side Components. Esto desde el punto de vista arquitectónico de componentes. Sin embargo, no olvidemos que el equipo de React desde el 2020 ya estaba hablando de React como Server Side Components. Lo que trato de decir es que aunque lo de Next es novedoso, no es la primera vez que se piensan componentes resueltos desde el servidor.

Por otra parte, Remix, un framework que extiende React pero es 100% Server Side, propone el término Full stack components nos permite focalizarnos en las interfaces de usuario, y seguir los estándares web para crear experiencias de usuario rápidas y resilientes. Con Remix podemos crear tanto el frontend como el backend de nuestras aplicaciones web, utilizando React como base. Entonces, podemos crear componentes React y reglas de renderizado de HTML para nuestro frontend, así como programar rutas manejables en nuestro backend para crear reglas y lógica de negocio.

A diferencia del comportamiento "común" de React, Remix utiliza server-side rendering para manipular los datos y renderizar el contenido y markup HTML en el servidor, enviando la menor cantidad posible de código JavaScript al cliente, y sin ninguna función fetchData o hook de efecto de lado del cliente que maneje sus solicitudes asíncronas.

La función que permite esto reciben el nombre de loaders, las cuales, aunque se enuncien a la misma altura de la UI, se resuelven del lado del servidor. Los datos son inyectado al componente (UI) por medio de un hook llamado useLoaderData.

TankStack Router plantea un paradigma similar. Desde su sistema de enrutamiento pone a disposición el método createBrowserRoute el cual es un arreglo de objetos en el que no solo se anidan los componentes que responde a paths, sino también la propiedad loader la cual se encarga de resolver todas las solicitudes asíncronas, y servir el componente junto con los datos resueltos desde el render inicial.

Finalmente, llegamos a React Router en su versión más reciente v6.8.1 - v6.8.2 en la que cambia por completo el paradigma, y se ajusta a esta tendencia de componentes robustos, resueltos con mínimo Js para resolver del lado del cliente.
Los Elementos BrowserRouter, Routes, Route, ahora son reemplazados por createBrowserRouter, el cual es un método con propiedades como error, path, loader, children. (Sí, un loader similar a los mencionados en anteriormente). En su propiedad children, recibe un arreglo de objetos en los que se declara el path, el elemento que corresponde al componente, y dos propiedades más: action, loader. Action se refiere a los eventos que detona el usuario cuando interactúa con nuestra app. Loader se refiere a los datos o solicitudes asíncronas necesarias en nuestro componente. Al igual que Remix, se pone a disposición el hook useLoaderData para extraer los datos de la solicitud sin ayuda de useEffect, ni ninguna otra librería o función asíncrona que suceda del lado del cliente.

Todas estas herramientas mencionadas tienen en común que abrazan comportamientos nativos del browser para habilitar estos métodos, y sus cálculos computacionales. Si eres un programador de más de 10 años de experiencia, seguramente dirías "like the good old days", un expresión romántica que hace alusión a un momento en el que el paradigma había alcanzado su máxima capacidad tanto en el lado del Servidor como del lado del Cliente. Sin embargo, sí propone reconciliar lo mejor de un paradigma antiguo, con el paradigma que ahora conocemos.

Recurso: https://codesandbox.io/s/serene-mountain-dwfsmk?file=/src/App.jsx

Top comments (0)