Skip to main content

Gestión de Estado

Redux Store Configuration

Configuración Principal

// src/store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';

import authSlice from './slices/authSlice';
import menuSlice from './slices/menuSlice';
import tempDataSlice from './slices/tempDataSlice';

const persistConfig = {
key: 'root',
storage,
whitelist: ['auth', 'menu', 'tempData']
};

const rootReducer = combineReducers({
auth: authSlice,
menu: menuSlice,
tempData: tempDataSlice
});

const persistedReducer = persistReducer(persistConfig, rootReducer);

export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
}
})
});

Auth Slice

Estado de Autenticación

// src/store/slices/authSlice.ts
interface AuthState {
user: User | null;
profiles: Profile[];
token: string | null;
isAuthenticated: boolean;
}

const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
setUser: (state, action) => {
state.user = action.payload.user;
state.token = action.payload.token;
state.isAuthenticated = true;
},
setProfiles: (state, action) => {
state.profiles = action.payload;
},
logout: (state) => {
state.user = null;
state.token = null;
state.isAuthenticated = false;
state.profiles = [];
}
}
});

Selectores

export const getUser = (state: RootState) => state.auth;
export const getToken = (state: RootState) => state.auth.token;
export const getProfiles = (state: RootState) => state.auth.profiles;
export const isAuthenticated = (state: RootState) => state.auth.isAuthenticated;

Estado de Navegación

// src/store/slices/menuSlice.ts
interface MenuState {
menuItems: MenuItem[];
activeRoute: string;
sidebarCollapsed: boolean;
}

const menuSlice = createSlice({
name: 'menu',
initialState,
reducers: {
setMenuItems: (state, action) => {
state.menuItems = action.payload;
},
setActiveRoute: (state, action) => {
state.activeRoute = action.payload;
},
toggleSidebar: (state) => {
state.sidebarCollapsed = !state.sidebarCollapsed;
}
}
});

Custom Hooks

useRedux Hook

// src/hooks/useRedux.tsx
export const useRedux = () => {
const dispatch = useAppDispatch();
const selector = useAppSelector;

return { dispatch, selector };
};

useAuth Hook

// src/hooks/useAuth.tsx
export const useAuth = () => {
const { user, isAuthenticated } = useSelector(getUser);
const dispatch = useDispatch();

const login = useCallback((credentials) => {
// Lógica de login
}, [dispatch]);

const logout = useCallback(() => {
dispatch(authSlice.actions.logout());
}, [dispatch]);

return { user, isAuthenticated, login, logout };
};

useMenu Hook

// src/hooks/useMenu.tsx
export const useMenu = () => {
const menuItems = useSelector(getMenuItems);
const dispatch = useDispatch();

const setActiveRoute = useCallback((route: string) => {
dispatch(menuSlice.actions.setActiveRoute(route));
}, [dispatch]);

return { menuItems, setActiveRoute };
};

Persistencia de Datos

Configuración Redux Persist

const persistConfig = {
key: 'root',
storage,
whitelist: ['auth', 'menu', 'tempData'], // Solo persistir estos slices
blacklist: ['notifications'] // No persistir notificaciones
};

Hidratación del Estado

// app/providers.tsx
export function Providers({ children }: { children: React.ReactNode }) {
return (
<Provider store={store}>
<PersistGate loading={<Spinner />} persistor={persistor}>
<ChakraProvider theme={theme}>
{children}
</ChakraProvider>
</PersistGate>
</Provider>
);
}

Estado Local vs Global

Cuándo usar Redux

  • Estado global: Autenticación, menús, configuración
  • Estado compartido: Datos entre múltiples componentes
  • Estado persistente: Datos que deben sobrevivir al cierre

Cuándo usar useState

  • Estado local: Formularios, modales, toggles
  • Estado temporal: Datos que no se comparten
  • Estado de UI: Loading states, errores locales

Ejemplo de Decisión

// ✅ Estado global - Información del usuario
const user = useSelector(getUser);

// ✅ Estado local - Estado de formulario
const [formData, setFormData] = useState({});

// ✅ Estado local - Loading específico
const [isLoading, setIsLoading] = useState(false);

Middleware y Efectos

Configuración de Middleware

export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
}
}).concat(
// Middleware personalizados
authMiddleware,
loggerMiddleware
)
});

Auth Middleware

const authMiddleware: Middleware = (store) => (next) => (action) => {
if (action.type === 'auth/logout') {
// Limpiar localStorage y redirigir
localStorage.clear();
window.location.href = '/auth/sign-in';
}

return next(action);
};

Optimización del Estado

Normalización de Datos

// En lugar de arrays anidados
const badState = {
properties: [
{ id: 1, owner: { id: 1, name: 'Juan' } },
{ id: 2, owner: { id: 1, name: 'Juan' } }
]
};

// Usar entidades normalizadas
const goodState = {
entities: {
users: { 1: { id: 1, name: 'Juan' } },
properties: {
1: { id: 1, ownerId: 1 },
2: { id: 2, ownerId: 1 }
}
}
};

Selectores Memoizados

import { createSelector } from '@reduxjs/toolkit';

const selectProperties = (state: RootState) => state.properties;
const selectFilter = (state: RootState) => state.filter;

export const selectFilteredProperties = createSelector(
[selectProperties, selectFilter],
(properties, filter) => {
return properties.filter(property =>
property.title.toLowerCase().includes(filter.toLowerCase())
);
}
);