Ruteo en aplicaciones de Redux y React.js

Usar Redux para almacenar el estado de una aplicación es genial. Y resulta que la ruta actual de la aplicación es, en sí, parte del estado de la misma. Tiene sentido entonces que eso se guarde en el Store de Redux.

Eso nos permitiría guardar el estado almacenado en Redux y al volver a aplicarlo y que la aplicación este en la misma ruta que lo dejamos con los mismos datos.

react-router

React-router es una librería para manejar rutas en aplicación de React.js. Con esta se nos facilita el decidir que componentes renderizar dependiendo de la ruta. El problema es que si la usamos junto a Redux estas dos librerías no trabajan en conjunto, para lograr esto existe react-router-redux.

react-router-redux

Con esta librería podemos hacer que la ruta actual de nuestra aplicación se guarde en nuestro Store de Redux, de esta forma todo el estado de nuestra aplicación es manejado por Redux, el cambio de ruta pasaría a despachar una acción que nos permite modificar el estado en base a esta.

Veamos como usarlo. Primero necesitamos instalar nuestras dependencias:

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

Como funciona

Con esta librería usamos tanto React Router como Redux tal cual lo indica su documentación. La librería se encarga de sincronizarlas ambas usando un reducer de Redux y una función que combina el Store con el History de React Router que vayamos a usar.

Implementación

Lo primero que vamos a hacer es crear nuestro Store usando el reducer que nos da React Router Redux.

import { combineReducers, createStore } from 'redux';
import { routerReducer } from 'react-router-redux';
import todos from './reducers/todos';
export default createStore(combineReducers({
todos,
routing: routerReducer,
});

Este reducer se encarga de guardar en el Store la ruta actual de la aplicación.

Una vez hecho esto vamos a definir las rutas y combinar React Router con Redux.

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { Router, Route, browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
// el Store que creamos antes
import store from './store';
// nuestros componentes de React para la App y las vistas
import App from './container/App';
import Home from './container/Home';
// sincronizamos el browserHistory de React Router con el Store
const history = syncHistoryWithStore(browserHistory, store);
// renderizamos nuestra app
render(
<Provider store={store}>
{/*le decimos al Router que use nuestro history sincronizado*/}
<Router history={history}>
{/*armamos las rutas de nuestra aplicación*/}
<Route component={App}>
<Route path="/" component={Home} />
</Route>
</Router>
</Provider>,
document.getElementById('app')
);

Ahora, cada vez que naveguemos por nuestra aplicación el history sincronizado va a pasar el cambio de ruta a Redux y luego a React Router para que cambie los componentes renderizados.

Cambiando el Store usando acciones

React Router Redux también nos provee de un middleware que nos permite usar cinco acciones propias para cambiar las ruta mediante código.

Para eso al crear nuestro Store debemos aplicar primero nuestro middleware.

import {
applyMiddleware,
combineReducers,
createStore,
} from 'redux';
import { routerReducer } from 'react-router-redux';
import todos from './reducers/todos';
// importamos el history sincronizado
import history from './history';
// combinamos nuestros reducers
const reducers = combineReducers({
todos,
routing: routerReducer,
});
// creamos y exportamos el store con el middleware aplicado
export default createStore(
reducers,
applyMiddleware(routerMiddleware(history))
);

Una vez aplicado el middleware a nuestro Store podemos usar los creadores de acciones que nos trae React Router Redux para cambiar la ruta.

import {
push,
replace,
go,
goBack,
goForward,
} from 'react-router-redux';
import store from './store';
// el objeto location que usamos en push y replace
// pathname = un string con lo que viene luego del / (incluyéndolo)
// search = un string con lo que viene luego del ? (incluyéndolo)
// query = un objeto con el string de search parseado
// state = un objeto con datos extras que no van en la URL
const location = {
pathname: '/hola',
search: '?name=mundo',
query: { name: 'mundo' },
state: {},
}
// agregamos un nuevo location y lo volvemos el actual (cambia la URL)
store.dispatch(push(location));
// reemplaza el location actual por el indicado
store.dispatch(replace(location));
// se mueve N posiciones en el historial (positivo o negativo)
store.dispatch(go(N));
// se mueve a la anterior posición del historial (igual a go(-1))
store.dispatch(goBack());
// se mueve a la siguiente posición del historial (igual a go(1))
store.dispatch(goForward());

Con estas acciones podemos, mediante acciones de Redux, cambiar la ruta actual de nuestra aplicación fácilmente.

Como me entero cuando cambia la ruta?

En algunos casos vamos a querer saber cuando el usuario cambia a otra vista de nuestra aplicación, por ejemplo si usamos Google Analytics. Eso se puede lograr simplemente usando history.listen().

Este método de nuestro history recibe una función que como único argumento recibe el objeto location cada vez que el Store se actualice. De la siguiente forma.

history.listen(location => {
// hacemos algo cuando cambia la ruta
});

Acción de Redux

Además de usar history.listen podemos, dentro de nuestros reducers, escuchar la acción LOCATION_CHANGE que se despacha luego de cada cambio del history y nos permite reaccionar a cambios de rutas desde cualquier reducer de nuestra aplicación.

Conclusión

Controla desde Redux el estado de nuestra aplicación incluyendo al ruta nos permite tener en Redux control total de nuestra aplicación, pudiendo serializarlo y volver al mismo estado incluyendo al ruta e incluso controlar mediante el despacho de acciones el cambio de ruta como vimos arriba.