Combinando React.js y Redux.js

En un artículo anterior vimos como funciona Redux.js y dijimos que era posible usarlo con cualquier framework o librería de JavaScript.

Y, aunque esto es cierto, Redux es especialmente bueno al usarlo con librerías como React.js, ya que podés describir tu UI como funciones puras y usar Redux para tener todo el estado de nuestra aplicación y pasarlo a nuestras vistas.

Instalando react-redux

La conexión de React con Redux no esta incluida directamente en Redux, para esto necesitamos bajar react-redux, así que vamos a descargar lo necesario:

npm i -S react react-dom react-redux redux

Encapsulando la aplicación

Lo primero que necesitamos es encapsular nuestra aplicación con el componente Provider que trae react-redux. Este componente recibe un único parámetro llamado store el cual es, como su nombre indica, la instancia del Store que usamos.

import React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";

import store from "./store";
import App from "./components/App";

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("app")
);

Este componente Provider define en el contexto global de React nuestra instancia del store.

Accediendo al Store

Una vez encapsulada nuestra aplicación de React nos toca definir que componentes van a acceder a nuestro Store, ya que no todos lo van a necesitar.

Para hacer eso necesitamos conectar nuestros componentes a Redux, esto se logra con un decorador que trae react-redux llamado connect.

// importamos el decorador @connect de react-redux
import { connect } from "react-redux";
import React from "react";
import UserItem from "./UserItem";

// aplicamos el decorador @connect a nuestro componente
@connect()
class UserList extends React.Component {
  render() {
    // renderizamos el listado de usuarios que
    // recibimos como props del Store
    return (
      <section>
        {this.props.users.map(user => (
          <UserItem {...user} key={user.id} />
        ))}
      </section>
    );
  }
}
export default UserList;

De esta forma nuestro componente UserList va a tener dentro de sus props todos los datos del Store. Con esto ya podemos renderizar nuestra aplicación usando los datos almacenados en el Store de Redux.

Optimizando

Aunque el método anterior sea más que suficiente no es lo mejor a nivel de performance, ya que de esta forma cada vez que cambie algo del Store se va a volver a renderizar UserList, incluso si la lista de usuario no cambio.

Para mejorar esto el decorador connect puede recibir una función que define que datos pasar al componente conectado.

function mapStateToProps(state, props) {
  // armamos un objeto solo con los
  // datos del store que nos interesan
  // y lo devolvemos
  return {
    users: state.users,
  };
}

// aplicamos el decorador @connect pasándole
// nuestra función mapStateToProps
@connect(mapStateToProps)
class UserList extends React.Component {
  ...
}

De esta forma podemos solo enviar a UserList el listado de usuarios, así cuando se modifique otra cosa que no sea la lista de usuarios no se va a volver a renderizar el componente.

Despachando acciones

Entre las props que el decorador connect inyecta a nuestro componente la función dispatch del Store, con la cual podemos despachar acciones.

// cargamos nuestro creador de acciones
import sendData from '../actions/send-data';

@connect()
class UserList extends React.Component {
  handleSendData() {
    const action = sendData();
    // despachamos la acción al store
    this.props.dispatch(action);
  }
  ...
}

Resulta que connect como segundo argumento podemos pasarle una función que nos permite controlar la función dispatch para mandar una personalizada.

import sendData from '../actions/send-data';
// importamos el método bindActionCreators de Redux
import { bindActionCreators } from 'redux';

function mapStateToProps(state, props) { ... }

function mapDispatchToProps(dispatch, props) {
  // creamos un objeto con un método para crear
  // y despachar acciones fácilmente y en
  // una sola línea
  const actions = {
    sendData: bindActionCreators(sendData, dispatch),
  };

  // devolvemos nuestras funciones dispatch
  // y los props normales del componente
  return { actions };
}

// decoramos nuestro componente pasándole las
// funciones mapStateToProps y mapDispatchToProps
@connect(mapStateToProps, mapDispatchToProps)
class UserList extends React.Component {
  handleSendData() {
    // creamos y despachamos la acción sendData
    this.props.actions.sendData();
  }
  ...
}

De esta forma podemos mandar a nuestro componente las acciones que necesitamos y que con solo ejecutarlas ya haga el dispatch de estas.

Funcionamiento sin decoradores

Aunque connect esta pensado como decorador es posible usarlo como una función normal sin necesidad de usar Babel con el plugin babel-plugin-transform-decorators-legacy para soportar decoradores de la siguiente forma.

export default connect(mapStateToProps, mapDispatchToProps)(Component);

Como siempre connect recibe mapStateToProps y mapDispatchToProps como parámetros, solo que además devuelve una función que recibe el componente a conectar y nos devuelve el componente conectado, el cual simplemente exportamos y listo, conseguimos el mismo resultado que usándolo como un decorador.

Esto es util si no queremos usar decoradores todavía, ya que la actual propuesta es muy posible que cambie a futuro.

Conectando componentes puros

Aunque lo normal es usar connect con componentes hechos con clases es completamente posible usarlo con componentes puros si hacemos uso del decorador como una función.

import React from 'react';
import { connect } from 'react-redux';
function mapStateToProps(state, props) { ... }
// el componente puro a conectar
function UserItem(props) { ... }
// exportamos el componente conectándolo gracias a connect
export default connect(mapStateToProps)(UserItem);

Así podríamos crear toda nuestra aplicación solamente con componentes puros, sin necesidad de usar clases. Esto nos obligaría, de verdad, a mantener el estado de toda nuestra aplicación en Redux y dejar React para la UI.

Conclusión

Integrar React y Redux es bastante simple y gracias a connect es muy fácil controlar que datos del Store le llegan a cada componente permitiendo una mejor performance y evitando renders innecesarios.