Lemoncode blog


Artículos, ideas, desarrollos, notas y mucho más

Viewing entries in
Frontend

Componentes de Clase en React: Inconvenientes

Introducción

Hasta hace unos meses, los componentes de clase de React (también llamados stateful components) estaban de moda. Pero eso se acabó con la llegada de los Hooks. ¿Por qué han dejado de ser una buena idea? En este post vamos a enumerar los principales inconvenientes a la hora de trabajar con componentes de clase en React.

La pesadilla del “this

En componentes de clase, el valor que adopta this dentro de una función dependerá de cómo sea invocada dicha función. Cuando nuestra función es utilizada como manejador de eventos (event handler), el valor al que apunta this será undefined. ¿Por qué? Las declaraciones y expresiones de clase (como por ejemplo nuestra función) se ejecutan en modo estricto, bajo el cual se aplica el binding por defecto a undefined y no a la instancia del componente como cabría esperar.

Más información al respecto aqui: https://medium.freecodecamp.org/this-is-why-we-need-to-bind-event-handlers-in-class-components-in-react-f7ea1a6f93eb

Por tanto, el this en un event handler disparado fuera de nuestro componente de clase, perderá la referencia a la instancia a la que pertenece.

Si ejecutamos el siguiente ejemplo y hacemos click en el botón Change Text, obtendremos el error:

Cannot read property ‘setState’ of undefined
export class MyHelloComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { myText: "hello" };
  }

  onChangeText() {        
    this.setState({ myText: "world" });
  }

  render() {
    return (
      <>
        <h3>{this.state.myText}</h3>
        <button onClick={this.onChangeText}>Change text</button>
      </>
    );
  }
}

La solución pasa por “atar” manualmente el this a la instancia de clase en cada una de las funciones declaradas como métodos de clase. Para ello contamos con dos mecanismos, o bien hacer un binding explícito en el constructor o bien utilizar funciones flecha experimentales (fat arrow) como método de clase (ver linea afectada marcada con el caracter “+”).

export class MyHelloComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { myText: "hello" };
+    this.onChangeText = this.onChangeText.bind(this);
  }

  onChangeText() {        
    this.setState({ myText: "world" });
  }

  render() {
    return (
      <>
        <h3>{this.state.myText}</h3>
        <button onClick={this.onChangeText}>Change text</button>
      </>
    );
  }
}

Demo: https://codesandbox.io/s/71opm61rzq

Esta característica de las clases puede llegar a convertirse en una fuente de problemas. ¿Cuántas veces has olvidado hacer adecuadamente el bind a tus event handlers, obteniendo finalmente un error en tiempo de ejecución?

Estado monolítico y funcionalidad difícilmente extraíble

En un componente de clase, tenemos un único lugar donde definir todo el estado al completo, y al mismo tiempo, una única forma de actualizarlo. ¿Inconveniente? Es complicado separar intereses y extraer la funcionalidad para su reuso. Recordemos que la separación de intereses (separation of concerns) y la reutilización de código (code reuse) son dos de los más importantes principios de programación que aparecerán en cualquier manual de buenas prácticas.

En el siguiente ejemplo, tenemos dos conceptos diferentes encapsulados en el mismo estado de clase:

export class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { color: "teal", name: "John", lastname: "Doe" };
  }

  getFullname() {
    return `${this.state.name} ${this.state.lastname}`;
  }

  render() {
      return (
          <div style={{ background: this.state.color }}>
              <h3>{this.getFullname()}</h3>
          </div>
      )
  }
}

Demo: https://codesandbox.io/s/yp9v4yyr8x

Veamos como podríamos implementar el mismo escenario usando hooks. En este caso podremos separar el estado para cada concepto:

import React from "react";

export const MyComponent = () => {
  const [color, setColor] = React.useState("teal");
  const [clientInfo, setClientInfo] = React.useState({name: 'John', lastname: 'Doe'});

  const getFullname = () => {
      return `${clientInfo.name} ${clientInfo.lastname}`;
  }

  return (
    <div style={{ background: color }}>
      <h3>{getFullname()}</h3>
    </div>
  );
};

Demo: https://codesandbox.io/s/ppom543mj7

Podemos dar un paso más y encapsular la funcionalidad relativa al usuario en un custom hook:

const useClientInfo = (name, lastname) =>  {
  const [clientInfo, setClientInfo] = React.useState({
    name,
    lastname,
  });

  const getFullname = () => {
    return `${clientInfo.name} ${clientInfo.lastname}`;
  };

  return {clientInfo, setClientInfo, getFullname}
}

export const MyComponent = () => {
  const [color, setColor] = React.useState("teal");
  const {getFullname} = useClientInfo('John', 'Doe');

  return (
    <div style={{ background: color }}>
      <h3>{getFullname()}</h3>
    </div>
  );
};

Demo: https://codesandbox.io/s/ly7wlq9mo9

Gestión de intereses relacionados en manejadores separados

Cuando usamos componentes de clase en React, disponemos de distintos eventos durante su ciclo de vida en los cuales ejecutar código: componentDidMount, componentDidUpdate, componentWillUnmount, etc. Podría darse el caso de que cierta funcionalidad relacionada tenga que ser dividida entre los distintos handlers del ciclo de vida del componente. Por ejemplo:

Definamos un componente padre:

export class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { visible: false };
  }

  render() {
    return (
      <>
        {this.state.visible && <MyChildComponent />}
        <button onClick={() => this.setState({ visible: !this.state.visible })}>
          Toggle Child component visibility
        </button>
      </>
    );
  }
}

Ahora, un componente hijo que simplemente mostrará la información de usuario:

export class MyChildComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { name: "John", lastname: "Doe" };
  }

  componentDidMount() {
    console.log("Hey Im mounting");
    console.log(`${this.state.name} ${this.state.lastname}`);
  }

  componentDidUpdate() {
    console.log("Just updating...");
    console.log(`${this.state.name} ${this.state.lastname}`);
  }

  componentWillUnmount() {
    console.log("bye bye, unmounting...");
  }

  render() {
    return (
      <div>
        <h3>
          {this.state.name} {this.state.lastname}
        </h3>
        <input
          value={this.state.name}
          onChange={e => this.setState({ name: e.target.value })}
        />
        <input
          value={this.state.lastname}
          onChange={e => this.setState({ lastname: e.target.value })}
        />
      </div>
    );
  }
}

Demo: https://codesandbox.io/s/oqyo3159jq

Mediante el empleo de hooks podemos agrupar toda esta funcionalidad en una única función:

const MyChildComponent = () => {
  const [userInfo, setUserInfo] = React.useState({name: 'John', lastname: 'Doe'})

  React.useEffect(() => {
    console.log('called when the component is mounted and right after it gets updated');

    return () => console.log('Clean up from the previous render before running effect next time ... ');
  })

  return (
    <div>
      <h3>
        {userInfo.name} {userInfo.lastname}
      </h3>
      <input
        value={userInfo.name}
        onChange={e => setUserInfo({ ...userInfo, name: e.target.value })}
      />
      <input
        value={userInfo.lastname}
        onChange={e => setUserInfo({ ...userInfo, lastname: e.target.value })}
      />
    </div>
  );
}

En este ejemplo, el código declarado dentro del useEffect es ejecutado una vez que el componente se ha montado pero también justo después de cada render. Por otro lado, la función de limpieza (devuelta en el useEffect) se ejecuta después de que el componente se haya montado y después de cada ejecución del efecto.

Demo: https://codesandbox.io/s/5zllr3k09p

Bien, pero … ¿y si quiero ejecutar un trozo de código solo cuando el componente se haya montado y hacer la limpieza cuando el componente vaya a desmontarse? Para ello, podemos jugar con el segundo parámetro del useEffect (en el ejemplo marcado con “-” las lineas que se eliminarían, con “+” las líneas que se añaden).

const MyChildComponent = () => {
  const [userInfo, setUserInfo] = React.useState({name: 'John', lastname: 'Doe'})

  React.useEffect(() => {
-    console.log('called just when the component is mounted and when after it gets updated');
+    console.log('called just when the component is mounted');

-    return () => console.log('Clean up from the previous render before running effect next time ... ');
+    return () => console.log('Clean up executed just when the component gets unmounted ... ');
-  })
+  }, [])

  return (
    <div>
      <h3>
        {userInfo.name} {userInfo.lastname}
      </h3>
      <input
        value={userInfo.name}
        onChange={e => setUserInfo({ ...userInfo, name: e.target.value })}
      />
      <input
        value={userInfo.lastname}
        onChange={e => setUserInfo({ ...userInfo, lastname: e.target.value })}
      />
    </div>
  );
}

Demo: https://codesandbox.io/s/q91qjql8r4

Ruido al utilizar High Order Components

Un HOC o High Order Component es una excelente forma de añadir nuevas capacidades a tu componente mediante composición, sin embargo:

  • Necesitas refactorizar su interfaz para poder aplicarlo.

  • Encadenar diversos HOC simultáneamente puede ser una pesadilla.

Veamos un ejemplo mínimo. El siguiente componente se emplea para saludar al usuario que está actualmente logado:

import React from "react";

export const MyComponent = (props) => {
  return (
    <>
      <h3>Hello: </h3>
    </>
  )
}

Para inyectar el usuario que está actualmente logado podemos crear un HOC:

const withUserInfo = (ComponentToWrap) => (props) =>
  <>
    <ComponentToWrap {...props} user="John" />
  </>
Let's make usage of this HoC in MyComponent

- export const MyComponent = (props) => {
+ const 

Podemos hacer uso de este HOC en MyComponent del siguiente modo:

const MyComponentInner = (props) => {  
  return (
    <>
      <h3>Hello: {props.user}</h3>
    </>
  )
}

export const MyComponent = withUserInfo()

Demo: https://codesandbox.io/s/l7qznjjyw9

Esta forma de añadir funcionalidad es bastante potente, pero, ¿no sería más sencillo y expresivo un mecanismo con el que podamos decir: “quiero usar esta funcionalidad”? Veamos como implementar este comportamiento utilizando hooks:

import React from "react";

const useUserInfo = () => {
  const [userInfo, setUserInfo] = React.useState('John');

  return {userInfo, setUserInfo}
}

export const MyComponent = (props) => {
  const {userInfo} = useUserInfo();

  return (
    <>
      <h3>Hello: {userInfo}</h3>
    </>
  )
}

Demo: https://codesandbox.io/s/pwj6z446xq

Ventajas:

  • No necesitamos reescribir el interfaz de nuestro componente: borrar el export, crear un componente para envolverlo, etc.

  • Sin complicaciones al tener anidando HOCs.

  • El contrato es claro, no tenemos que adivinar que propiedades está inyectando el HOC a nuestro componente.

Migrando componentes de su forma funcional a clase, y también en el sentido contrario

Cuando iniciamos la implementación de un componente tratamos de mantenerlo sencillo, por lo que es típico comenzar con un componente funcional. Un ejemplo simple podría ser el siguiente: mostrar un mensaje de bienvenida a un usuario (nos lo informa el padre a través de la propiedad userName).

import React from "react";

export const MyComponent = () => {
  return (
    <>
      <MyChildComponent userName="John"/>
    </>
  )
}

export const MyChildComponent = (props) => {
  return (
    <>
      <h3>Hello: {props.userName}</h3>      
    </>
  )
}

Demo: https://codesandbox.io/s/985wyr1olw

Supongamos que, en lugar de recibirlo desde el padre, queremos almacenarlo en el estado local de nuestro componente. Una aproximación clásica sería migrar nuestro componente a su forma stateful, es decir, refactorizarlo por completo, prestando atención de no equivocarnos con el this.state, añadiendo su constructor, implementando el método render, etc. Algo tal que así:

export const MyComponent = () => {
  return (
    <>
      <MyChildComponent/>
    </>
  )
}

export class MyChildComponent extends React.Component {  
  constructor(props) {
    super(props);
    this.state = {userName: 'John'}
  } 

  render() {
      return (
        <>  
         <h3>Hello: {this.state.userName}</h3>      
        </>
      )
  }
}

Demo: https://codesandbox.io/s/40l5k4q1o9

¿Cómo podría haber sido este refactor utilizando hooks? Tan sencillo como esto:

export const MyComponent = () => {
  return (
    <>
     <MyChildComponent/>
    </>
  )
}

export const MyChildComponent = (props) => {
  const [userName]  = React.useState('John');
  return (
    <> 
     <h3>Hello: {userName}</h3>      
    </>
  )
}

Demo: https://codesandbox.io/s/jlkoxm6plw

Combinando componentes funcionales y de clase

Por último, pero no menos importante, en un proyecto de dimensiones considerables, una aproximación mixta donde componentes funcionales y de clase tengan que convivir juntos podría traer diversos inconvenientes de mantenibilidad:

  • Inconsistencia en nuestra base de código.

  • Dificultad añadida para que nuevos desarrolladores se inicien en el proyecto, teniendo que aprender ambas formas y decidir cuando es más conveniente una u otra, o cuando deben refactorizar un componente existente en uno u otro sentido.

  • La refactorización de componentes, ya sea en un sentido u otro, es una tarea propensa a errores, por ejemplo: se nos olvida eliminar el this, nos equivocamos en la sintaxis de clase vs función, errores con la función de render, etc.

Resumiendo

Antes de tirarnos a la piscina con la nueva característica de moda, es importante aprender por qué la necesitamos y que problemas viene a resolver. Esperamos que este artículo pueda ayudaros a entender mejor por qué los hooks han generado tanta expectación en la comunidad y se han convertido en un estándar de facto. En los siguientes artículos de esta serie comenzaremos a indagar en los detalles que hacen a los hooks tan poderosos, mostrando su cara amable y también los casos frontera, basándonos en casos de uso en proyectos reales. ¡Estad atentos!

Esperamos que hayáis disfrutado el artículo. ¡Muchas gracias!

Comment

Javascript Asíncrono: La guía definitiva

La asincronía es uno de los pilares fundamentales de Javascript. ¿Nunca has llegado a entender en profundidad como funciona? ¿Crees que es magia lo que sucede al utilizar promesas? Entra en esta guía definitiva y aprende de una vez por todas como funciona la asincronía en Javascript. Su comprensión mejorará en gran medida tu código y te ayudará a escribir mejores aplicaciones. 

19 Comments

Curso de React: Preguntas y respuestas

Hace unos días que impartimos el curso de React en vivo donde se cubrieron desde conceptos básicos de la propia librería a cómo adentrarnos en el desarrollo de una aplicación web real con esta tecnología.

Durante el curso de esta formación los alumnos realizaron preguntas muy interesantes tanto durante el curso en vivo como por el canal de slack que abrimos para el mismo. Hemos decidido seleccionar un grupo de preguntas y material en este post, espero que te sea interesante.

Por cierto si tienes ganas de aprender React, puedes adquirir la grabación de este curso por sólo 29 € pinchando en este enlace.

¿Cómo arrancar un proyecto con React desde cero? ¿Qué necesito?

Prerrequisitos

Necesitarás tener instalado Node. Al instalar Node, podemos abrir una terminal y ejecutar el comando `npm` para listar los paquetes necesarios.

Boilerplate

Tienes en el repositorio react-training-ts (o react-training-es6 para la versión en ES6) todos los ejemplos estructurados con su código fuente junto con un README.md explicando paso a paso para ejecutarlo. En el ejemplo 02 Sample App / 00 Boilerplate se encuentra una plantilla que puedes utilizar como punto de inicio o configuración inicial.

IDE / editor

Como editor podemos usar, por ejemplo, Atom o VisualStudio Code.

Plugins necesarios para trabajar con Atom:
- En el caso de usar TypeScript: atom-typescript
- En el caso de ES6, language-babel para el highlight del código, auto-complete JSX, etc.

Otros plugins de Atom que te pueden ayudar:

Comment

Typings 1.X y Unable to Find "react"

Hace poco que actualizamos a la versión 1.X de Typings y nos encontramos con una sorpresa, lo que antes funcionaba sin problemas:


typings install react --ambient --save

Nos da ahora un mensaje de error bien feo Unable to find "react" ("npm") in the registry

Después de buscar un poco encontramos varias pistas:

  • En este post nos explica que tenemos que indicar ahora el origen del paquete.
  • En el breaking changes de typings nos comentan cambios en los parametros de la línea de comandos...

Configurando tsconfig en Atom y VS Code

Una de las cosas más apasionantes en el mundo del Front-end es lo rápido que está evolucionando, hace unos meses estábamos usando bower y tags “script” en el html de nuestros sitios web y ahora nos encontramos utilizando “imports” y dejando que una herramienta de bundling nos lo resuelva todo.

Otro tema apasionante es el abanico de herramientas que tenemos disponibles, de utilizar Eclipse o Visual Studio, pasamos a tener una cantidad de opciones muy interesantes: Sublime, Atom, Webstorm, Visual Studio Code…

Ahora viene la parte negativa ¿Cómo narices haces que todo esto funcione bien en los diferentes editores?

Por nuestra parte nos hemos esforzado en que nuestros proyectos publicados se abran correctamente con Atom y Visual Studio Code (nos comentan que con Webstorm también va la cosa bien). En este post vamos a resumir los “tortazos” que nos hemos dado y las soluciones que hemos aplicado, espero que os sea de utilidad...

React: Rethinking Best Practices

Mucha gracias a todos los que asististeis al webinar de “React: Rethinking best practices”. 

En este post os pasamos el material de la charla:

  • La grabación del webinar la tenéis disponible en este link
  • Te puedes descargar la presentación en este link
  • Sobre las demos:
    • Jugando con JSX lo tenéis en este repositorio y tenéis este post disponible
    • Sobre las demos de React y Redux, las tenéis disponibles en este repositorio

Si estáis interesados en profundizar y aprender más sobre React + Redux, tenemos un curso disponible: que además vuestra empresa podría bonificar por la tripartita y hasta saliros a coste cero. Más información en este link o escríbenos un correo a formacion@lemoncode.net

Comment

JSX / TSX ¿Qué tiene de bueno?

Una de las cosas que nos sorprende cuando le echamos un ojo a React es que el “HTML” parece estar embebido dentro de los ficheros JavaScript… De primeras, esto ha hecho que muchos desarrolladores hayamos descartado esta tecnología al considerar que era una vuelta al Spaghetti y rompíamos principios como el de “Separation of concerns”.

En mi caso, no podía entender cómo se rompía tan flagrantemente las buenas prácticas y cómo a la vez, empresas muy grandes como Facebook, Airbnb, Uber, Yahoo y muchas otras, lo abrazaban como estándar ¿Hay algo que nos estamos perdiendo? Vamos a indagar un poco en qué consiste esto...

2 Comments