Immutable.js en reducers de Redux

Resumen

Implementar inmutabilidad en Redux se vuelve mucho más sencillo cuando delegas esa responsabilidad a una librería especializada. Con Immutable.js puedes garantizar que tu estado nunca se modifique directamente, lo que permite que tu UI en React renderice solo cuando hay cambios reales. Es una herramienta clave si trabajas con aplicaciones que manejan estados complejos.

¿Qué es Immutable.js y por qué usarlo en Redux?

Immutable.js es una librería que reemplaza los objetos y arreglos planos de JavaScript por estructuras de datos diseñadas para ser inmutables. En lugar de un object, trabajas con un Map. En lugar de un array, trabajas con una List. También tienes Set y otras estructuras que puedes consultar en su documentación oficial [01:05].

¿Qué hace exactamente Immutable.js? Convierte tus objetos y arreglos en estructuras especiales que no pueden mutarse. Cada vez que cambias algo, devuelve una nueva referencia, lo que Redux necesita para detectar cambios.

La instalación es directa desde la terminal con npm i immutable [00:45]. En Visual Studio Code puedes abrir y cerrar la terminal con el shortcut Command + J para agilizar el flujo.

¿Cómo convertir el estado inicial con fromJS?

El primer cambio ocurre en el reducer. Como Immutable trabaja con sus propias estructuras, el initialState ya no puede ser un objeto plano. Aquí entra el método fromJS, que toma cualquier objeto o arreglo de JavaScript y lo transforma en estructuras de Immutable [01:40].

js import { fromJS } from 'immutable';

const initialState = fromJS({ pokemons: [], loading: false, });

A partir de ese momento, ya no accedes con notación de punto. Necesitas usar los métodos propios de la librería para leer y escribir valores.

¿Cómo modificar el estado con setIn?

El método setIn recibe dos parámetros: un arreglo con los niveles de profundidad a los que quieres acceder, y el nuevo valor que vas a colocar ahí [02:30]. Por ejemplo, para asignar la lista de pokémones:

js return state.setIn(['pokemons'], fromJS(action.payload));

Nota cómo el payload, que llega como arreglo plano de JavaScript, también pasa por fromJS para mantener la consistencia de las estructuras dentro del estado.

¿Cómo leer valores con get y getIn?

Para obtener valores usas get cuando es un solo nivel, y getIn cuando necesitas navegar por varios niveles. Por ejemplo, encontrar el índice de un pokémon dentro de la lista:

js const currentPokemonIndex = state .get('pokemons') .findIndex(pokemon => pokemon.get('id') === action.payload.id);

El findIndex funciona casi igual que en JavaScript nativo, pero como iteras sobre estructuras de Immutable, accedes a las propiedades con get en lugar de notación de punto [04:10].

Para leer si un pokémon es favorito, puedes encadenar gets o usar getIn con la ruta completa:

js const isFavorite = state.getIn(['pokemons', currentPokemonIndex, 'favorite']);

¿Cómo actualizar propiedades anidadas en el reducer?

Para marcar un pokémon como favorito, ya no necesitas crear copias manuales del arreglo. Immutable se encarga de devolver un nuevo estado con el cambio aplicado [05:20].

js return state.setIn( ['pokemons', currentPokemonIndex, 'favorite'], !isFavorite );

Este patrón elimina la fricción de hacer spread de objetos y arreglos, que en estados profundos se vuelve tedioso y propenso a errores.

¿Cuándo uso fromJS y cuándo no? Usa fromJS cuando el valor que entra es un objeto o arreglo. Si es un valor primitivo como un boolean o un string, lo asignas directo sin convertirlo.

Por eso, en el caso de setLoading, simplemente haces:

js return state.setIn(['loading'], action.payload);

¿Cómo consumir el estado inmutable desde React?

En el componente, el useSelector ahora recibe estructuras de Immutable. Acceder con notación de punto deja de funcionar, así que necesitas usar get para leer cada propiedad [07:15].

El problema aparece cuando tu UI espera objetos planos, por ejemplo, al iterar sobre la lista de pokémones en PokemonList. Tienes dos caminos:

  • Reemplazar cada acceso por get('name'), get('id'), etc. Funciona, pero es invasivo.
  • Usar toJS() para convertir la estructura de Immutable de vuelta a un objeto plano de JavaScript.

La segunda opción es más limpia:

js const pokemons = useSelector(state => state.get('pokemons').toJS());

Así mantienes el resto del componente intacto y conservas la legibilidad del debugging en el inspector.

¿Qué ves en consola cuando logueas el estado?

Si haces un console.log del estado, vas a notar que cada objeto aparece como un Map y cada arreglo como una List. En el root puedes inspeccionar los entries del map [09:30]. Esa visibilidad confirma que el estado realmente es inmutable y que la librería está haciendo su trabajo.

La ventaja final es la abstracción: tú escribes código como si trabajaras con objetos planos, mientras Immutable garantiza la integridad del estado por debajo. ¿Cómo te fue al implementar el loader por tu cuenta? Cuéntame en los comentarios qué decisiones tomaste.