HoC o Componentes de orden superior en React

Seguro que a todos los que estáis inmersos en el mundillo React os sonará en patrón HoC o
Higher-Order Components, lo que en castellano serían Componentes de Orden Superior. En
este post me gustaría hacer una introducción a este patrón que ha demostrado ser de gran
utilidad a la hora de desarrollar aplicaciones React. En este post veremos que son los HoC y como trabajar con ellos.

 

React hoc o componentes de orden superior

 

Por supuesto, como viene siendo habitual en este blog escribiré el código en ES6, así que si aún no estás al día de esta versión de JavaScript te recomiendo que no pierdas más tiempo y antes de seguir leyendo le pegues un buen repaso.

Al lio!

 

¿Que son los HoC?

Algo bastante común cuando desarrollamos aplicaciones React es que varios componentes compartan la misma funcionalidad, si ya llevas tiempo trabajando con React te sonarán bastante los descatalogados Mixins, pues se podría decir que los HoC cumplen una función similar.

Básicamente son funciones que toman como argumento otro componente, extienden su funcionalidad y devuelven un nuevo ‘super componente’. Si estáis familiarizados con la programación funcional, es lo mismo que las funciones de orden superior pero trasladado a los componentes.

Normalmente este patrón se implementa como una función que básicamente recibe uno o varios componentes (u otros objetos) como argumentos y devuelve un componente nuevo que implementa las funcionalidades del HoC. Si los datos del HoC cambian, este se renderizará de nuevo y actualizará el componente envuelto.

 

Creando nuestro primer HoC

Para ver las cosas más claras vamos a crear un ejemplo bastante básico que nos servirá para comprender como funcionan los Componentes de Orden Superior.

Algo bastante común en el desarrollo de aplicaciones es que ciertas vistas o componentes de la app dependan de ciertas validaciones o condiciones para ser renderizados. Podríamos crear un HoC que se encargue de manejar dichas validaciones en cada uno de los componentes que las necesiten, liberando así a dichos componentes de esa lógica y ahorrando mucho código ya que no tendremos que realizar las comprobaciones en cada componente.

Para esto crearemos un HoC que llamaremos ValidateHoC de la siguiente manera:

import { Component } from "React"

const ValidateHoC = (ComposedComponent, condition) => class extends Component {
  constructor (props) {
    super(props)
  }

  render() {
    return condition 
               ? <ComposedComponent {...this.props} { ...this.state } /> 
               : null
  }
}

Y ahora podremos utilizarlo ‘envolviendo’ cualquiera de nuestros componentes que requieran de validación:

class HeaderComponent extends Component {
  render () {
    return <div>Hello World!</div>
  }
}

export default ValidateHoC(SuperComponent, true)

Como se puede ver en el ejemplo hemos definido nuestra función ValidateHoC que toma como parámetros un React.Component y una condición. Para no extendernos demasiado, la condición del ejemplo es un valor boleano. Como podéis ver esta función devuelve un React.Component que tras aplicar la condición renderizará o no el componente que toma como argumento. Sencillo, verdad?

 

Decorando componentes

Los Componentes de Orden Superior se pueden utilizar de varias maneras, aunque las más común suele ser la que hemos visto en el ejemplo, también podemos utilizar los @decorator de ES7. Para los que aún no sepáis de que va os recomiendo que le deis un repaso a este otro post en el que hablo sobre el tema.

Para poder utilizar nuestro ValidateHoC como un @decorator tan solo hemos de declarar una función que tome como argumento la condición y devuelva otra función con el React.Component como argumento y a su vez devuelva nuestro ValidateHoC con dicha condición y el Component.

function Validate(condition) {
  return function(Component) {
    return ValidateHoC(Component, condition)
  }
}

@Validate(true)
class HeaderComponent extends Component {
  render () {
    return <div>Hello World!</div>
  }
}

export default SuperComponent

 

HoC en el mundo real

Ahora que tenemos claro que son y como podemos utilizar los Componentes de Orden Superior vamos a ver un ejemplo del mundo real.

Un caso bastante común suele ser que un React.Component necesite consumir datos de una API. Normalmente haríamos las llamadas necesarias para obtener dichos datos desde el método componentDidMount de nuestro componente, pero lo cierto es que esta solución no es muy optima.

Vamos a crear un Componente de Orden Superior que se encargue de realizar esta tarea por nosotros. Necesitaremos pasarle como argumentos, además del componente que vamos a envolver, los parámetros de entrada necesarios para para la llamada a la API. De momento podríamos hacer algo así:

import { Component } from "React"

const fetchDataHoC = (ComposedComponent, config) => class extends Component {
  state = { data: null }

  constructor (props) {
    super(props)
  }

  componentDidMount () {
    fetch(config.url, {
      method: config.method,
      body: config.body
    })
    .then(this.checkFetchStatus)
    .then(this.parseJSON)
    .then(data => {
      console.log(data)
      this.setState({ data })
    })
    .catch(error => {
      const _error = new Error(error)
      throw _error
    })
  }

  render() {
    return this.state.data 
           ? <ComposedComponent {...this.props} { ...data } /> 
           : <PreloaderComponent />
  }
}

Como podemos observar, nuestro HoC se encarga de hacer la llamada a la API con los parámetros que configuremos en el componente que lo utilice. Mientras los datos no estén disponibles, este renderizará una recarga, una vez los datos estén disponibles, devolverá el nuevo componente con los nuevos datos.

Podríamos hacer este componente algo más ‘funcional’ añadiendo un argumento más que sea el componente que actuará como precarga pero no me quiero extender demasiado en esto ya que no es el objetivo del post. En cualquier caso, os animo a que le deis una vuelta y penséis como lo podríais optimizar.

class MyComponent extends Component {
  constructor (props) {
    super(props)
  }

  render () {
    return <div>this.props.data</div>
  }
}

const config = {
  url: 'https://your.api.url.here',
  method: 'GET',
  body: {}
}

export default fetchDataHoC(MyComponent, config)

Sencillo verdad? De este modo nuestros componentes serán mucho mas ligeros y quedarán liberados de la lógica de negocio. Además evitamos repetir código.

 

Conclusión

Sin duda los HoC son una herramienta muy util y potente a la hora de trabajar en aplicaciones React, ayudándonos a evitar la repetición de código y a conseguir una estructura mucho más solida y consistente en nuestras aplicaciones.

Espero que el ejemplo, aunque no muy funcional, os haya servido para comprender como funcionan y os sirva de guía para crear vuestros propios Componentes de Orden Superior.

 

Hasta la próxima!