Actions y action creators
Una acción es un objeto plano con un campo type que describe qué pasó en el sistema; no una orden que dice qué hacer, sino un hecho que narra qué ocurrió. Esta lección separa el evento del comando —la diferencia entre todos/todoAdded y SET_TODOS—, define la anatomía de una acción (type, payload, meta, error) según la convención Flux Standard Action, y presenta los action creators como fábricas que encapsulan la forma de la intención. Se cierra con el 2026: cómo createAction y createSlice de Redux Toolkit generan tipos y creators sin que escribas el objeto a mano, y por qué el type serializable es lo que hace posibles el log, el replay y el time-travel.
Si el reducer es el verbo de Redux, la acción es el sustantivo. Una acción es la unidad mínima de información que entra en el sistema: un objeto plano y serializable que declara que algo sucedió. Y aquí se esconde la decisión de diseño más sutil y más ignorada de todo Redux: una acción no es una orden que le das al estado, es una noticia que le cuentas. La diferencia entre “haz esto” y “esto ocurrió” parece retórica, pero determina si tu store será un conjunto legible de eventos o una pila de setters disfrazados. Quien confunde acción con comando escribe Redux sintácticamente correcto y arquitectónicamente muerto.
- Definir la acción como objeto plano con un
typeobligatorio y serializable. - Distinguir modelar eventos —qué pasó— de modelar comandos —qué hacer—.
- Conocer la anatomía Flux Standard Action:
type,payload,meta,error. - Usar action creators y ver cómo
createActionlos genera en 2026.
Un objeto plano con un type
Una acción es, literalmente, un objeto JavaScript corriente. Su único requisito absoluto es un campo type, y por convención ese type es una cadena de texto legible que nombra el evento en formato dominio/evento. Todo lo demás —los datos que acompañan al evento— vive en el resto de campos. No hay clases, no hay herencia, no hay métodos: la austeridad es el punto.
// La accion minima: solo un type.
{ type: 'contador/reiniciado' }
// Una accion con datos: el payload transporta lo que el reducer necesitara.
{ type: 'todos/todoAgregado', payload: { id: 't1', texto: 'leer a Elm' } }
Que sea un objeto plano y serializable no es un capricho estético: es la condición que habilita todo lo demás. Como una acción es JSON puro, se puede registrar en un log, guardar en disco, enviar por la red a otra pestaña o a un servidor, y volver a reproducir más tarde. El type serializable es la clave de bóveda del time-travel: rebobinar el estado es volver a aplicar la misma lista de acciones, y eso solo es posible porque cada acción es un dato inerte y no una llamada a función irrepetible.
Usa el formato dominio/evento —carrito/productoAgregado, sesion/usuarioSalio— y trátalo como un identificador estable, no como un texto que puedas cambiar a la ligera. Muchos reducers pueden reaccionar al mismo type, así que renombrarlo es refactorizar un contrato, no editar una etiqueta. Evita las mayúsculas gritonas heredadas de los ejemplos antiguos; la convención de 2026, que Redux Toolkit adopta, es dominio/eventoEnPasado.
Eventos, no órdenes
Aquí está el corazón filosófico de la lección. La tentación del principiante es tratar las acciones como setters: SET_USUARIO, SET_CARGANDO, ACTUALIZAR_TOTAL. Cada una dice al estado qué hacer y con qué valor. Funciona, y es un error de diseño. La forma correcta es que la acción describa qué pasó en el mundo —sesion/inicioSesion, checkout/pagoConfirmado— y deje que cada reducer decida cómo reaccionar. La metáfora oficial de la documentación de Redux es exacta: piensa en una acción como el titular de un periódico que narra un suceso, no como una instrucción que ejecutas.
La consecuencia práctica de esta distinción es enorme y no se ve hasta que la app crece. Si modelas comandos, cada trozo de estado que deba cambiar ante un mismo suceso necesita su propio comando, y la vista se convierte en la orquestadora que sabe demasiado: tiene que despachar SET_CARRITO_VACIO, SET_TOTAL_CERO y SET_CUPON_NULO una tras otra. Si modelas eventos, la vista despacha un solo checkout/pedidoCompletado y cada reducer interesado —el del carrito, el del total, el del cupón— reacciona por su cuenta. Un evento, muchos oyentes; esa es la desnormalización del control que hace escalable a Redux.
Evento (correcto)
pedido/pagoConfirmado. Narra un hecho del dominio. Un solo despacho, y cada reducer interesado decide qué hacer con la noticia. La vista no sabe quién escucha.
Comando (a evitar)
SET_TOTAL, SET_CARRITO. Ordena a un trozo concreto de estado que tome un valor. La vista debe conocer y coordinar cada cambio. Acopla y no escala.
flowchart LR E[suceso del mundo] --> A[una accion evento] A --> R1[reducer carrito] A --> R2[reducer total] A --> R3[reducer historial] style A fill:#f9e2af,color:#11111b style R1 fill:#89b4fa,color:#11111b style R2 fill:#89b4fa,color:#11111b style R3 fill:#89b4fa,color:#11111b
Anatomía: Flux Standard Action
Como el objeto acción es libre, la comunidad estandarizó su forma para que las herramientas y el middleware puedan trabajar sin adivinar. La convención se llama Flux Standard Action y define cuatro campos con significado fijo: type obligatorio, payload con los datos, error como bandera booleana cuando la acción representa un fallo, y meta para información que no es ni el resultado ni el error —un identificador de petición, una marca de tiempo—.
| Campo | Papel | Obligatorio |
|---|---|---|
type |
Identifica el evento en formato dominio/evento |
Sí |
payload |
Los datos que transporta el evento | No |
error |
true si la acción representa un fallo |
No |
meta |
Metadatos ajenos al payload: requestId, timestamp | No |
Seguir esta forma no es burocracia: es lo que permite que un middleware genérico distinga una acción de error de una normal sin conocer tu dominio, o que las DevTools sepan dónde está el payload para mostrarlo. La estandarización de la forma es lo que convierte un ecosistema de piezas sueltas en herramientas que encajan.
Action creators: fábricas de intención
Escribir el objeto acción a mano en cada punto de despacho es frágil: un type mal tecleado no falla, simplemente no hace nada, y ese es de los bugs más silenciosos que existen. Un action creator es sencillamente una función que devuelve la acción ya formada; centraliza la estructura en un solo lugar, para que el resto del código exprese intención y no ensamble objetos.
// A mano, fragil: un typo en el type falla en silencio.
dispatch({ type: 'todos/todoAgregado', payload: { id, texto } })
// Con action creator: la forma vive en un solo sitio.
const todoAgregado = (id: string, texto: string) => ({
type: 'todos/todoAgregado' as const,
payload: { id, texto },
})
dispatch(todoAgregado('t1', 'leer a Elm'))
La grandeza de Redux no está en el store ni en el reducer por separado, sino en la línea que separa la acción de su interpretación. Cuando declaras que una acción describe qué pasó y no qué hacer, has partido tu sistema en dos mitades con responsabilidades limpias: el flujo de acciones es un relato objetivo de lo que ha sucedido —un log de eventos que podrías guardar, auditar y reproducir— y los reducers son la interpretación subjetiva de ese relato, la respuesta de cada dominio ante cada noticia. Esta separación es exactamente el patrón que en sistemas grandes se llama event sourcing, y no es coincidencia: Redux es event sourcing para la interfaz. Su virtud es que desacopla al emisor del receptor. La vista que despacha sesion/usuarioSalio no sabe ni le importa cuántos reducers reaccionan, ni si mañana añades uno nuevo que también quiera limpiarse ante ese evento; simplemente narra el hecho y sigue. Añadir comportamiento se vuelve aditivo —un reducer más que escucha— en lugar de invasivo —modificar la vista para que despache un comando más—. Quien interioriza esto deja de preguntar “qué debe cambiar cuando el usuario pulsa esto” y empieza a preguntar “qué acaba de pasar en el dominio”, que es la pregunta de la que cuelga toda arquitectura de eventos sana, de Redux a Kafka.
Redux Toolkit elimina casi todo este ceremonial. createAction('todos/todoAgregado') te devuelve un action creator ya tipado que además expone su propio type en una propiedad, de modo que el reducer y el creator comparten una única fuente de verdad para la cadena. Y createSlice va más lejos: derivas los action creators y sus type automáticamente a partir de los nombres de los reducers que escribes, sin declarar ninguna constante de cadena. El objeto plano con type sigue siendo lo que viaja por dentro —nada de esto cambia el modelo—, pero ya casi nunca lo tecleas tú.
- Toma un flujo de tu app modelado con setters (
SET_X,SET_Y) y reescríbelo como eventos del dominio en pasado (x/algoOcurrio). - Encuentra un suceso que deba cambiar tres trozos de estado y compruébalo: con eventos, ¿bastó un solo
dispatchy tres reducers escuchando? - Escribe un action creator para cada evento y verifica que ningún punto de despacho ensamble ya el objeto a mano.
- Clasifica los campos de tus acciones según Flux Standard Action: ¿qué es
payload, qué esmeta, dónde marcaserror? - Reemplaza tus creators manuales por
createActionde Redux Toolkit y observa cómo eltypedeja de ser una cadena repetida en dos sitios.