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.