Primeros pasos en redux
November 24, 2022

Hoy vamos a explicar los conceptos m谩s b谩sicos de Redux y React Redux de la forma m谩s concisa posible, primero explicando los conceptos m谩s importantes y luego viendo su implementaci贸n tanto en TypeScript "vanilla" como en React.
Redux
Redux es una biblioteca muy popular de JavaScript que permite manejar el estado de una aplicaci贸n. El estado de aplicaci贸n es un objeto global que maneja informaci贸n heterog茅nea usada por al aplicaci贸n, por poner un ejemplo, el estado para controlar la visibilidad de un spinner si estamos cargando datos en nuestra aplicaci贸n.
Otro de los grandes objetivos del control de estado es pasar informaci贸n entre componentes sin caer en el prop drilling o el tener que pasar un mismo valor entre m煤ltiples componentes padre-hijos para completar la cadena.
Para todo esto necesitamos bibliotecas de manejo de estados como Redux. Redux como biblioteca es muy popular porque basa su uso en unos principios muy sencillos e intuitivos, fundamentados en los siguientes patrones:
- Single Source Of Truth: O "煤nica fuente de veracidad", que b谩sicamente significa que solo tenemos un lugar (llamado
Store
) donde vamos a guardar el estado de toda la aplicaci贸n. - Inmutabilidad: En Redux no vamos a cambiar el estado de un objeto y sus propiedades directamente, si no que vamos a crear un nuevo objeto calculando el nuevo estado de la aplicaci贸n y as铆 actualizandolo con el nuevo objeto creado.
Componentes
Con estos principios, vamos a ver los componentes esenciales de Redux:
Store
El Store
o almacenamiento contiene el estado de la aplicaci贸n. Este Store
es un objeto con sus funciones y atributos al que podemos suscribirnos para escuchar eventos cuando el objeto Store
se actualiza. Este objeto es referenciado muchas veces como el "谩rbol de estado", ya que es posible almacenar cualquier valor y anidar tanta informaci贸n como se necesite.
Actions
Son objetos JavaScript que describen qu茅 a pasado en un estado, pero no como este estado debe cambiar. Simplemente mandamos (dispatch) estas acciones a nuestro objeto Store
siempre que queramos actualizar el estado. La gesti贸n del cambio ser谩 efectuada por los Reducers
.
Estas acciones son objetos que tienen al menos un atributo, en este caso por convenio es type
, para indicar la acci贸n, y luego el resto de atributos necesarios para indicar la acci贸n.
Reducers
Los Reducers
son funciones puras que definen como debe cambiar el estado de la aplicaci贸n. Cuando enviamos (dispatch) una Action
a nuestro Store
, la acci贸n pasa a un Reducer
que la interpretar谩 para modificar el estado.
Una funci贸n reducer
toma dos par谩metros, el estado previo y la acci贸n que vamos a enviar y devuelve un nuevo estado.
let reducer = (previousState, action) => newState
B谩sicamente un reducer computar谩 el nuevo estado de la aplicaci贸n bas谩ndose en el estado (y su tipo) que vamos a enviar. Lo m谩s normal es que a medida que una codebase se vuelve m谩s compleja, los reducers
aumentar谩n en funcionalidad y complejidad, es por ello que la mejor estrategia a seguir es dividirlos en m煤ltiples partes y luego combinarlos con la funci贸n combineReducers
.
Flujo
El flujo de la aplicaci贸n es relativamente sencillo conociendo los componentes, solo tenemos que utilizar en conjunto los elementos antes mencionados:
- Lo primero tendremos que suscribirnos al
Store
y tener un mecanismo para reaccionar. - Ahora en cualquier cambio de estado que queramos hacer, solo tenemos que llamar al m茅todo
store.dispatch()
con laAction
que queramos realizar. - Redux gestiona esta acci贸n mediante el
Reducer
. - Al estar suscritos al
Store
, nos devolver谩 el nuevo estado que podremos a帽adir a nuestra aplicaci贸n.
Como veis, incorporar Redux en nuestra aplicaci贸n es relativamente sencillo una vez tenemos todos los conceptos claros. Ahora vamos a ver unos ejemplos pr谩cticos para TypeScript y React.
Redux + TypeScript
Vamos a hacer primero una versi贸n en TypeScript vanilla para asentar los conceptos de forma correcta. Esta aplicaci贸n consta de un formulario sencillo con el que a帽adir recordatorios. Cada vez que pulsemos el bot贸n "Add", mandaremos una acci贸n a Redux con el recordatorio que queremos crear.
Primero vamos a definir una interfaz para los reminders, que constar谩 de un atributo title
para el t铆tulo y description
para la descripci贸n.
interface Reminder {
title: string;
description: string;
}
Ahora vamos a crear nuestra Action
. De momento solo vamos a crear un acci贸n para a帽adir nuevos recordatorios a nuestro estado global, es por ello que primero definiremos la interface
de nuestra acci贸n, seguido del atributo type
que controlaremos en nuestro reducer
y atributos adicionales como son title
y description
que servir谩n para crear el recordatorio.
// ----------- Action ------------
interface ReminderAction {
type: string;
title: string;
description: string;
}
export const ADD_REMINDER: string = 'ADD_REMINDER';
export function addReminder(title: string, description: string): ReminderAction {
return { type: ADD_REMINDER, title: title, description: description };
}
Ahora generaremos nuestro reducer
. Como es el primero de todos ser谩 nuestro rootReducer
, en el que controlaremos todas las acciones con un switch
(en este caso solo tenemos una inicialmente), en la que con un spread operator a帽adiremos un nuevo objeto Reminder
en el array de estados de la aplicaci贸n.
// ----------- Reducer ------------
interface ReminderState {
reminders: Reminder[];
}
const initialState: ReminderState = {
reminders: []
};
const rootReducer = (state: ReminderState = initialState, action: ReminderAction): ReminderState => {
switch(action.type) {
case ADD_REMINDER:
return {
reminders: [
...state.reminders,
{
title: action.title,
description: action.description,
},
],
};
default:
return state;
}
}
Ya solo nos falta crear nuestra store
y a帽adir nuestro rootReducer
para tener Redux configurado en nuestra aplicaci贸n.
// ----------- Store ------------
const store = createStore(rootReducer);
Para la UI, lo m谩s destacable es que vamos a tener una funci贸n llamada renderReminders
que b谩sicamente recoge el estado de nuestro Store
y modifica el DOM de la aplicaci贸n para mostrar todos los recordatorios que tengamos guardados. Para que nuestra aplicaci贸n reaccione a los cambios vamos a suscribirnos al Store
mediante el m茅todo store.subscribe(() => {})
al que le pasaremos nuestra funci贸n para renderizar los recordatorios.
// ----------- UI ------------
let button = document.getElementById("addButton");
let remindersDashboard = document.getElementById("reminder-dashboard");
button.onclick = function (event) {
const title = document.getElementById("titleInput").value;
const description = document.getElementById("descriptionInput").value;
store.dispatch(addReminder(title, description));
};
const renderReminders = () => {
const reminderState = store.getState().reminders;
let reminderDom = ``;
reminderState.forEach((element) => {
reminderDom += `
<div class="reminder-card">
<p class="reminder-title">${element.title}</p>
<p class="reminder-description">${element.description}</p>
</div>
`;
});
remindersDashboard.innerHTML = reminderDom;
};
store.subscribe(() => {
renderReminders();
});
Redux + React
Ahora vamos a reutilizar la mayor parte de la estructura del proyecto anterior, ya que las declaraciones de las Actions
, Reducers
y Store
son las mismas. Nuestro objetivo ahora es conectarnos y acceder a nuestro Store
a trav茅s de los componentes funcionales de nuestra aplicaci贸n. Para ello vamos a hacer uso de la librer铆a react-redux y de varios hooks
que nos facilitar谩n el trabajo.
Lo primero que vamos a tener que hacer es rodear nuestro componente de aplicaci贸n con el Provider
de Redux, al que le pasaremos el objeto store
que hemos declarado previamente. Con ello ya podremos acceder al Store
global desde cualquiera de los componentes de nuestra aplicaci贸n.
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.querySelector(".container"));
Vamos a hablar primero de useSelector
. Este hook permite devolver una parte del objeto Store
a nuestro componente. El selector va a hacer una comprobaci贸n del estado anterior, y si no es el mismo, el componente accionar谩 un renderizado. Este hook
acepta un segundo par谩metro que permite modificar la estrategia de comparaci贸n entre estados, en este caso usaremos shallowEqual
para que no renderice el componente si en el cambio de estado los valores permanecen igual.
const RemindersComponent = () => {
const reminders = useSelector((state) => state.reminders, shallowEqual);
return (
<div class="reminders-dashboard">
{ reminders.map((reminder, index) => (
<div className="reminder-card">
<p className="reminder-title">{reminder.title}</p>
<p className="reminder-description">{reminder.description}</p>
</div>
))}
</>
);
};
Por otro lado tenemos useDispatch()
, que nos permitir谩 mandar una acci贸n a nuestro Store
para que lo procese el reducer. Es tan f谩cil como llamar a la funci贸n y pasar la Action
que ya ten铆amos generada.
const App = () => {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const dispatch = useDispatch();
const dispatchReminder = () {
dispatch(addReminder(title, description));
}
return (
<div className="wrapper">
<HeaderComponent title={"Redux + React"} />
<FormComponent setTitle={setTitle} title={title} setDescription={setDescription} description={description} submitForm={dispatchReminder}/>
<RemindersComponent />
</div>
);
}
Con esto ya tendr铆amos configurado Redux en nuestro proyecto React. Como veis una vez dominamos los conceptos esenciales la implementaci贸n es relativamente sencilla. Abajo tenemos un codepen con el ejemplo mencionado antes.
Saludos 馃憢