Hace un tiempo decidí aprender React, la documentación me pareció bastante buena y siguiéndola puede hacer un pequeño proyecto. Ya habiendo cubierto las bases me puse a averiguar cómo era el estandar de trabajo en React. Llegué a react-redux, me decidí a escribir esto porque encontré que la documentación no es amigable para alguien con poca experiencia en Javascript.

Disclaimer: Cualquier corrección es bienvenida, soy principiante en React en general. Y por favor, lea esto desde un computador, luego de escribir el artículo, no sólo estoy molesto con mi css sino que también con WordPress, con suerte migraré este blog a React en algún tiempo. Al final está el link al proyecto.

¿Por qué usar react-redux?

Porque desacopla las vistas de los datos. Veamos un ejemplo.

El siguiente ejemplo es un caso de uso común, una lista a la izquierda de la cual se puede seleccionar un elemento, al ser seleccionado el elemento se puede ver el detalle.

Veamos lo mismo ahora pero con los componentes

¿Cómo se hace sólo con React?

Sólo con React sin react-redux podríamos hacerlo de la siguiente forma

Acá lo que ocurre es que el libro seleccionado y la lista de libros es parte del estado del componente App. El componente BookDetail recibe como sus props el libro seleccionado. El componente BookList recibe como parte de sus props, la lista de libros y un callback. El componente BookList le pasa a cada componente BookItem un libro y un callback. Cuando el componente BookItem es clickeado, a través del callback, le avisa al padre BookList cuál libro fue seleccionado. A su vez, el componente BookList le avisa a su padre el componente App el libro seleccionado, cuando App recibe el libro seleccionado cambia su estado, cambiando las props del componente BookDetail.

Esto es un serio problema, porque todos los componentes están acoplados. El lado positivo de esto es que tan sólo usando React y un par de funciones de callback, podemos hacer una aplicación relativamente compleja.

¿Cómo se hace con react-redux?

Con react-redux el componente App, lo único que hace es poner la BookList y el BookDetail, ya no están conectados. El componente BookDetail se va a conectar para tener el estado la lista de libros y para informar cuando un libro se haya seleccionado. El BookDetail se va a conectar para escuchar cuando un libro se haya seleccionado.

El lado positivo de esto es que tenemos componentes modulares y desacoplados. El lado negativo de esto es que react-redux no es una librería trivial de usar, y la curva de aprendizaje se hace mucho más empinada considerando que Javascript se puede escribir de muchas formas distintas.

¿De qué se trata react-redux?

react-redux es una librería que nos permite implementar un patrón de diseño conocido como publish-subscribe. La traducción más acorde viene a hacer transmisión-suscripción. El ejemplo que se usa para explicar esto es que es como una radio, la radio transmite y los que estén sintonizados en esa transmisión reciben el mensaje. En todos los lenguajes está este patrón de alguna forma, por ejemplo, en Android está en lo que se conoce como BroadcastReceiver.

Lamentablemente mis habilidades en Js no son todavía suficiente para entender lo que se hace bajo el capot, pero sí te puedo explicar qué ocurre conceptualmente. Se usa una función que se llama connect de la librería react-redux para que un componente pueda escuchar y/o enviar información. Un componente, puede sólo escuchar, puede sólo enviar o puede hacer ambos.

¿Qué tengo que saber para hacer esto?

Esto tengo que reconocer está bien explicado en la documentación, hay algunos alcances que quiero hacer.

  • actions: Es una función, que va a hacer la transmisión de la acción, siempre retornan un type que corresponde al tipo de acción y un payload que corresponde a la data del estado
  • reducer: Es una función que filtra el tipo de acciones y luego hace algo con el payload para retornar el estado
  • components: Son componentes de React sin lógica, lo único que hacen es pintar la web
  • container: Acá uno suele confundir porque semánticamente no es un contenedor porque contenga otros elementos dentro, se le llama container a un componente de React en donde se hace la conexión a escuchar y/o enviar transmisiones. Se suele decir que si un componente se transforma en contenedor entonces “we promote the componente to a container”

Hay un par de cosas en las que es bueno ahondar acá:

  • Las transmisiones que se esuchen o envien se hacen para cambiar el estado de la app, es un estado para todo la app, puede sonar agobiante, pero no lo es. En un principio uno piensa que una app compleja va a tener un estado super complejo, como si fuera un JSON super anidado, pero la mayoría de las veces es simplemente un listado de cosas y la otra parte del estado es el seleccionado de ese listado.
  • Cada action representa un type de action, por ejemplo una lista en donde cada fila tiene 2 elementos que son cliqueables, cada click gatilla una action distinta que retorna un objeto cuyo type es distinto. Es importante entender esto, porque las actions no están directamente conectados con los reducers. Cuando se gatilla cualquier action, todos los reducers escuchan, es por eso que cada reducer tiene un control de flujo (switch generalmente) que permite que se llame sólo cuando el type gatillado a través de la action está en el control de flujo: if action type 1 do something else do nothing.
  • Las actions, los reduces, los components y los containers, tienen su propio directorio nombrado homologamente. Las actions y los reducers son funciones, pueden estar dentro de 1 archivo o cada función en su propio archivo según sea más conveniente. Los components y los containers son componentes de React, pueden ser clases o funciones, según lo que más convenga, en casos simples, un component puede estar dentro de un container como una función.
  • Hay otras cosas que hay que utilizar, como un store que también es parte de react-redux y es lo que permite que react-redux funcione a nivel general. Y el combined reducers, que agarra todos los reducers y hace que lo que retornan sea el estado de la aplicación. Veremos este tipo de cosas más abajo.

El siguiente es un ejemplo de una action

Fíjense en lo que retorna, retorna un objeto que tiene un type y tiene un payload. El type va a servir para que los reducer puedan decir: si es que type X hago Z de lo contrario hago H. Y el payload, es lo que va a ser el estado, todavía no es el estado, pero lo va a ser en el reducer. El argumento que recibe viene desde donde se va a gatillar, si no lo necesitara puede no llevar ninguno.

El siguiente es un ejemplo de un reducer

Los reducer siempre, reciben un state y una action. En este caso, el state se está dejando por defecto como null, y la action contiene el type y el payload. El action es el objeto que retornó la action que vimos arriba, el type siempre se usa para filtrar cuáles transmisiones se van a procesar en el reducer y el payload es lo que acá se retorna como estado. Una suspicacia es que acá se retorna el payload como estado, pero todavía no se le dice a la app que es el estado, eso lo vamos a ver más adelante en el combine reducers.  Cualquier reducer puede hacer lo que quiera con el payload, le podría sumar uno, le podría restar uno, lo que sea, y luego de que lo haga, ahí es cuando retorna el estado ya procesado.

¿Cómo funciona el connect?

La función connect funciona con otras 2 funciones que son: mapStateToProps y mapDispatchToProps. Ambas de estas funciones confunden porque son automágicas. Así que es importante ser efusivo en resaltar lo que es supuestamente obvio.

mapStateToProps

El siguiente es una función mapStateToProps

La función siempre recibe el state como argumento (puede recibir un 2do argumento pero es más avanzado, no tiene cabida en el artículo). Ese state que recibe es el estado de la aplicación. Recuerden hasta el momento no hemos definido el estado de la aplicación, eso se hace en el combine reducers. Lo que hace esta función es retornar un objeto que mapea el estado de la aplicación con las props del componente en donde se está usando. En el lado izquierdo de lo que se retorna está “books” eso es el nombre que tienen la prop del componente. Y en el lado derecho está el estado de la aplicación. Está función todavía no está escuchando, sino que dijo cómo se va a escuchar y qué es lo que se va a escuchar. Lo que permite hacer esta función es que ahora podemos acceder a la props books dentro del componente, y esa props va a cambiar cuando cambie el estado de la aplicación.

mapStateToProps

El siguiente es una función mapDispatchToProps

Esta función siempre recibe un argumento dispatch, que dentro de sí simplemente utilizamos. Lo que hace esta función es que nos permite definir cómo se va a hacer la transmisión. Si se fijan en la imagen, el selectBook morado se está definiendo como el selectBook amarillo. El selectBook morado es un prop que por el acto de escribirlo ahí, se define y está validamente disponible para ser utilizado. El selectBook amarillo es una action que es una función importada en ese componente. Lo que se acaba de hacer entonces, es decir que cada vez que se use el prop selectBook se va a ejecutar la action selectBook (que es una función). En el fondo, lo que se acaba de definir es lo que se va a transmitir.

La clave está en los props

Fíjense en cómo se usan en el componente ahora

En la función mapStateToProps definimos el prop books. Ahora el prop books se puede usar para tener una lista de libros. Y en la función mapDispatchToProps definimos que la action selectBook iba a ser un prop con el mismo nombre, así que cada vez que se selecciona un libro, usamos el prop selecBook para transmitir la action selectBook.

¿Y connect cuando?

Ahora usamos el connect

Cosas en las que fijarse:

  • La función connect recibe las funciones que acabamos de definir, por lo tanto ahora este componente está escuchando y enviando transmisiones. Si quisiéramos sólo escuchar, pasamos sólo el 1er argumento sin 2do argumento. Si queremos sólo transmitir pasamos null al 1er argumento y mapDispatchToProps al 2do.
  • La función connect, se conecta con el componente haciendo 2 cosas. Poniendo el componente en un segundo par de paréntesis y siendo exportada como lo que se va a utilizar cuando se vaya a usar el componente para ponerlo en otro.

¿Cómo funciona el combine reducer?

Lo que nos falta ahora, es entonces definir el estado de la aplicación. Hasta el momento ya podemos hacer la conexión de un componente, pero a eso le falta recibir el estado de la aplicación y poder enviarlo. El siguiente es un combineReducers:

La función combineReducers es de redux así que hay que importarla, luego tenemos que importar todos los reducers que hemos creado. Lo que se hace acá es juntar todos los reducers que se hayan creado y se igualan al estado de la aplicación. Es muy importante fijarse acá en que:

  • Recuerden que más arriba se explicó que un reducer retorna un estado de la aplicación, pero que todavía no se ha asignado ese estado. Acá es dónde se hace esto.
  • El estado books acá es el mismo state.books que se usó en mapStateToProps, de esa forma el estado books puede ser efectivamente equivalente a la prop books en el componente.

En un diagrama sería algo así:

El diagrama es muy largo y no se ve bien, puede abrirlo en una ventana nueva

  • El estado de la aplicación juanito, se iguala al props juanito en el mapStateToProps
  • El estado de la aplicación juanito es el resultado de que se usó un action, action que fue recibido por todos los reducers pero qué sólo fue aceptado por los que en su control de flujo lo definieron como tal, y que esa action se despachara ocurrió porque se definió en el mapDispatchToProps la actionDeJuanito es igual a un prop del mismo nombre

Checklist para implementar react-redux

Para terminar de implementar esto hay que hacer un par de cosas adicionales, para los alcances de este artículo vamos a asumir que son siempre iguales (si queremos trabajar con promesas cambia un poco, eso es más avanzado). Un checklist de cómo usar react-redux debería ser así

  1. En el index.js use el ReactDOM.render pasánsole un Provider con la store (ver ejemplo)
  2. Cree los componentes, sin react-redux, ponga textos de prueba
  3. En un app.js hay que poner los componentes (ver ejemplo)
  4. Defina una action
  5. Cree un reducer para esa action
  6. Cree el combine reducer y defina el estado de la app para el reducer que acaba de hacer
  7. Vaya al componente y defina el mapStateToProps junto con el dispatchStateToProps
  8. Añada en el componente los props que necesita mostrar y los props que van a transmitir actions
  9. Haga el connect en el componente

El siguiente es el link al proyecto que se usó de ejemplo