Redux Toolkit
RTK existe para que no escribas a mano action types, creators, combineReducers, thunk suelto y DevTools. El paquete oficial es @reduxjs/toolkit. Si hoy escribes Redux, escribes RTK.
Documentación: Getting Started, createSlice, configureStore, Immer.
configureStore
Además de juntar slices:
- Middleware thunk (incluido).
- En desarrollo: avisos si mutas el state de verdad o metes valores no serializables.
- Conexión a Redux DevTools.
Puedes sustituir o concatenar middleware; el capítulo 7 lo hace con listeners. No instales redux-thunk aparte: ya viene.
createSlice: tareas
Un slice = nombre + estado inicial + reducers. RTK genera action creators y el reducer.
import { createSlice } from '@reduxjs/toolkit'
const tareasSlice = createSlice({
name: 'tareas',
initialState: { items: [], filtro: 'todas' },
reducers: {
tareaAnadida(state, action) {
state.items.push({
id: action.payload.id,
titulo: action.payload.titulo,
hecha: false,
})
},
tareaAlternada(state, action) {
const tarea = state.items.find((t) => t.id === action.payload)
if (tarea) tarea.hecha = !tarea.hecha
},
filtroCambiado(state, action) {
state.filtro = action.payload
},
},
})
export const { tareaAnadida, tareaAlternada, filtroCambiado } = tareasSlice.actions
export default tareasSlice.reducertareaAnadida({ id, titulo }) despacha algo como { type: 'tareas/tareaAnadida', payload: { id, titulo } }. No declares constantes TAREAS_ANADIDA.
Store:
import { configureStore } from '@reduxjs/toolkit'
import tareasReducer from './tareasSlice'
export const store = configureStore({
reducer: { tareas: tareasReducer },
})Immer: parece mutar, no muta el state real
Dentro de los reducers de createSlice (y createReducer) puedes escribir state.items.push(...) o tarea.hecha = !tarea.hecha. Immer intercepta esas escrituras y produce el siguiente estado inmutable. El store sigue siendo inmutable; DevTools sigue viendo un diff.
No concluyas “Redux ahora permite mutar el estado”. Fuera del receta de Immer (un reducer a mano, un listener que haga getState().tareas.items.push) sigues rompiendo el contrato. Tampoco mutes action.payload si vas a reutilizar el objeto.
En React
import { useDispatch, useSelector } from 'react-redux'
import { tareaAnadida, tareaAlternada } from './tareasSlice'
export function ListaTareas() {
const items = useSelector((state) => state.tareas.items)
const dispatch = useDispatch()
return (
<ul>
{items.map((t) => (
<li key={t.id}>
<button type="button" onClick={() => dispatch(tareaAlternada(t.id))}>
{t.hecha ? 'Hecha' : 'Pendiente'}
</button>
{t.titulo}
</li>
))}
</ul>
)
}Un carrito (lineas, cantidad) o un contador (increment) son el mismo patrón; las tareas evitan que todo el manual sea +1.
Errores habituales
- Un único slice
appde 80 campos: parte por feature (tareas,sesion). - Mutar el state después de
returno fuera del reducer. - Desactivar los checks de serialización “porque molestan” sin entender el valor no serializable.
Buenas prácticas
- Un fichero de slice por feature; exporta actions, reducer y selectors.
namedel slice estable: cambia eltypede todas las actions.- Lee el check de serialización como un aliado (persist, DevTools, RTK Query).
Ejercicio
- Añade
tareaEliminadaque quite uniddeitems. - Despacha
tareaAnadidayfiltroCambiadoy míralos en DevTools. - Intenta (en un reducer a mano, no en el slice) hacer
state.items.pushsin Immer y observa el aviso en desarrollo.
Siguiente paso
Continúa con Slices y async thunks.
