Normalización de estado
Anidar autores dentro de cada tarea duplica datos y complica un rename:
tareas: [
{ id: 't1', titulo: 'Pan', autor: { id: 'u1', nombre: 'Ana' } },
{ id: 't2', titulo: 'Leche', autor: { id: 'u1', nombre: 'Ana' } },
]Cambiar el nombre de Ana implica recorrer todas las tareas. La forma normalizada guarda cada entidad una vez y relaciona por id:
usuarios.ids / usuarios.entities
tareas.ids / tareas.entities (autorId: 'u1')No todo el state debe ser { ids, entities }. Un filtro: 'todas' o un wizardStep se quedan planos. Normaliza colecciones que actualizas por id o compartes entre pantallas.
Documentación: Normalizing State Shape, createEntityAdapter.
createEntityAdapter
RTK genera reducers CRUD y selectors memoizados sobre { ids, entities }. El id por defecto es entity.id.
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'
const tareasAdapter = createEntityAdapter({
sortComparer: (a, b) => a.titulo.localeCompare(b.titulo),
})
const usuariosAdapter = createEntityAdapter()
const tareasSlice = createSlice({
name: 'tareas',
initialState: tareasAdapter.getInitialState({ filtro: 'todas' }),
reducers: {
tareasRecibidas: tareasAdapter.setAll,
tareaUpsert: tareasAdapter.upsertOne,
tareaQuitada: tareasAdapter.removeOne,
},
})
const usuariosSlice = createSlice({
name: 'usuarios',
initialState: usuariosAdapter.getInitialState(),
reducers: {
usuariosRecibidos: usuariosAdapter.setAll,
},
})
export const tareasSelectors = tareasAdapter.getSelectors((state) => state.tareas)
export const usuariosSelectors = usuariosAdapter.getSelectors((state) => state.usuarios)setAll sustituye la colección (carga inicial). upsertOne crea o mezcla campos. updateOne espera { id, changes }.
Relación: la tarea guarda autorId, no el objeto autor. El nombre se resuelve en un selector o en la vista:
const tarea = tareasSelectors.selectById(state, 't1')
const autor = tarea ? usuariosSelectors.selectById(state, tarea.autorId) : undefinedselectAll devuelve el array ordenado; selectById es O(1) de cara al mapa.
Qué no normalizar
- Un único
sesion.usuarioId(no hay colección). - Borradores efímeros de un formulario.
- Respuestas de RTK Query: Query ya cachea por endpoint/argumento. No copies esa lista a
tareas.entities“para tenerla en Redux” (la tienes dos veces). Si necesitas ids para UI cliente, deriva con un selector sobreapi.endpoints...o guarda solo selección (tareaActivaId).
Errores habituales
- Normalizar un array de tres flags.
- Mutar
entities[id]fuera del adapter (pierdesidsalineado). - IDs no estables (índice del array como id).
Buenas prácticas
getInitialState({ filtro })para mezclar metadatos con la tabla.- Selectors del adapter exportados junto al slice.
- Relaciones por id; joins en selectors, no en cada reducer.
Ejercicio
- Parte un JSON anidado (tarea + autor) en dos
setAll. - Renombra un usuario y comprueba que las tareas no necesitan update.
- Deja
filtrofuera deentitiesy léelo con un selector propio.
Siguiente paso
Continúa con RTK Query.
