Creando código modular con ducks de Redux

En un artículo anterior hablamos que una buena práctica al momento de ordenar nuestro código de Redux.js es usar el formato de módulos ducks.

Este formato nos dice que nuestros módulos deben tener sus tipos de acciones, sus creadores de acciones y su reducer en un solo archivo, y debe exportar estos últimos dos para que sean usados en nuestra aplicación.

Ahora vamos a ver como podemos crear un módulo usando este formato fácilmente con una librería que nos ahorra mucho trabajo.

Instalando dependencias

Primero vamos a instalar lo que necesitamos:

npm i -S redux redux-duck immutable
  • redux-duck: Librería utilitaria para crear ducks fácilmente.
  • Immutable: Librería para trabajar con estructuras de datos inmutable.

Creando nuestro duck

Vamos a crear un duck para controlar un listado de mensajes en una aplicación de chat. Para eso vamos a crear un archivo donde vamos a tener nuestro módulo, dentro vamos a colocar el siguiente código.

import { createDuck } from 'redux-duck';

Primero importamos la función createDuck de redux-duck.

const duck = createDuck('messages', 'chat');

Luego vamos a crear nuestro duck, el primer parámetro que vamos a pasar es el nombre del mismo, el segundo es el nombre de la aplicación, este parámetro es opcional.

Definiendo tipos de acciones

const ADD_MESSAGE = duck.defineType('ADD_MESSAGE');
const REMOVE_MESSAGE = duck.defineType('REMOVE_MESSAGE');

Luego vamos a definir los tipos de acciones que vamos a tener en nuestro módulo. El método defineType recibe como único parámetro un string con el nombre de la acción y devuelve un nuevo string con el formato:

app-name/duck-name/ACTION_NAME

Donde el nombre de la aplicación es opcional, como dijimos antes. En nuestro ejemplo quedarían dos strings con este formato:

ADD_MESSAGE === 'chat/messages/ADD_MESSAGE'
REMOVE_MESSAGE === 'chat/messages/REMOVE_MESSAGE'

Creando nuestros creadores de acciones

export const addMessage = duck.createAction(ADD_MESSAGE);
export const removeMessage = duck.createAction(REMOVE_MESSAGE);

Luego vamos a exportar el resultado de ejecutar el método createAction pasándole los tipos de acciones que definimos antes.

Este método nos devuelve una función que crea objetos de acciones con la propiedad type igual al valor que le indicamos al crearla. Esta función puede recibir cualquier valor como parámetro y lo va a definir como payload de la acción devuelta.

Creando nuestra función reductora

const initialState = {
list: Immutable.List(),
data: Immutable.Map(),
};
export default duck.createReducer({
[ADD_MESSAGE]: (state, { payload }) => {
return {
list: state.list.push(payload.id + ''),
data: state.data.set(payload.id + '', payload),
};
},
[REMOVE_MESSAGE]: (state, { payload }) => {
return {
list: state.list.filterNot(id => id === payload.id),
data: state.data,
};
},
}, initialState);

Por último creamos y hacemos un export default del valor devuelto por el método createReducer.

Este recibe dos parámetros, el primero es un objeto cuyos nombres de propiedades sean los strings creados anteriormente al definir los tipos de acciones y los valores sean funciones que reciben el estado y la acción.

Y como segundo parámetro recibe el estado inicial de nuestro reducer, este puede ser un string o un objeto. Esta función nos devuelve nuestra función reductora (reducer) que luego exportamos para que se pueda usar.

Código final

// importamos createDuck de redux-duck
import { createDuck } from 'redux-duck';
// importamos Immutable para usarlos luego en nuestro estado inicial
import Immutable from 'immutable';
// creamos nuestro duck
const duck = createDuck('messages', 'chat');
// definimos los tipos de acciones
const ADD_MESSAGE = duck.defineType('ADD_MESSAGE');
const REMOVE_MESSAGE = duck.defineType('REMOVE_MESSAGE');
// creamos nuestros creadores de acciones
export const addMessage = duck.createAction(ADD_MESSAGE);
export const removeMessage = duck.createAction(REMOVE_MESSAGE);
// definimos el estado inicial de nuestro
const initialState = {
list: Immutable.List(),
data: Immutable.Map(),
};
// creamos nuestra función reductora (reducer)
export default duck.createReducer({
[ADD_MESSAGE]: (state, { payload }) => {
return {
list: state.list.push(payload.id + ''),
data: state.data.set(payload.id + '', payload),
};
},
[REMOVE_MESSAGE]: (state, { payload }) => {
return {
list: state.list.filterNot(id => id === payload.id),
data: state.data,
};
},
}, initialState);

Conclusión

Modularizar nuestro código en ducks nos ayuda tener código más fácil de mantener, probar y reutilizar, y la librería redux-duck nos facilita crear estos módulos de una forma sencilla y organizada.