Overview
DADDO uses Redux for centralized state management with Redux Thunk middleware for handling asynchronous operations. The state is organized into logical slices, each managed by its own reducer.
Store Configuration
The Redux store is configured with Redux Thunk middleware and Redux DevTools support:
import { createStore , applyMiddleware , compose } from "redux" ;
import { thunk } from "redux-thunk" ;
import rootReducer from './Reducer' ;
const composeEnhancer = window . __REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose ;
const store = createStore (
rootReducer ,
composeEnhancer ( applyMiddleware ( thunk ))
);
export default store ;
The store uses Redux DevTools for development debugging and Redux Thunk for async action creators.
Root Reducer Structure
The root reducer combines multiple slice reducers using combineReducers:
src/Redux/Reducer/index.js
import { combineReducers } from "redux" ;
import authReducer from "./authReducer" ;
import productReducer from "./productReducer" ;
import { catalogReducer } from "./catalogReducer" ;
import { sellsReducer } from "./sellsReducer" ;
import { categoriesReducer } from "./categoriesReducer" ;
import { filteredProductsReducer } from "./filteredProdReducer" ;
import { dashboardReducer } from "./dashReducer" ;
const rootReducer = combineReducers ({
auth: authReducer ,
products: productReducer ,
catalog: catalogReducer ,
sells: sellsReducer ,
categories: categoriesReducer ,
filteredProducts: filteredProductsReducer ,
dashboard: dashboardReducer
});
export default rootReducer ;
State Shape
The global Redux state follows this structure:
{
auth : {
token : string | null ,
user : Object | null ,
loading : boolean ,
error : string | null
},
products : {
products : Array ,
loading : boolean ,
error : string | null
},
catalog : {
catalog : Array ,
catalogs : Array ,
loading : boolean ,
error : string | null ,
businessName : string | null ,
phone : string | null
},
sells : {
sells : Array ,
currentSell : Object | null ,
loading : boolean ,
error : string | null
},
categories : { ... },
filteredProducts : { ... },
dashboard : {
loading : boolean ,
salesByDay : Array ,
salesByMonth : Array ,
topProducts : Array ,
salesByUser : Array ,
error : string | null
}
}
Reducers Deep Dive
Auth Reducer
Manages authentication state with token persistence:
src/Redux/Reducer/authReducer.js
const token =
localStorage . getItem ( "token" ) ||
sessionStorage . getItem ( "token" );
const user =
localStorage . getItem ( "user" ) ||
sessionStorage . getItem ( "user" );
const initialState = {
token: token ?? null ,
user: user ? JSON . parse ( user ) : null ,
loading: false ,
error: null ,
};
export default function authReducer ( state = initialState , action ) {
switch ( action . type ) {
case "LOGIN_REQUEST" :
return { ... state , loading: true , error: null };
case "LOGIN_SUCCESS" :
return {
... state ,
user: action . payload . user ,
token: action . payload . token ,
loading: false ,
error: null ,
};
case "LOGIN_FAILURE" :
return {
... state ,
error: action . payload ,
loading: false ,
};
case "LOGOUT" :
localStorage . clear ();
sessionStorage . clear ();
return { token: null , user: null , loading: false , error: null };
case "UPDATE_USER_SUCCESS" :
localStorage . setItem ( "user" , JSON . stringify ( action . payload ));
return { ... state , user: action . payload , loading: false };
default :
return state ;
}
}
The auth reducer initializes state from localStorage/sessionStorage, enabling persistent login sessions across page refreshes.
Product Reducer
Handles product CRUD operations:
src/Redux/Reducer/productReducer.js
const initialState = {
products: [],
loading: false ,
error: null ,
};
export default function productReducer ( state = initialState , action ) {
switch ( action . type ) {
case "GET_PROD_REQUEST" :
case "UPDATE_PROD_REQUEST" :
return { ... state , loading: true , error: null };
case "GET_PROD_SUCCESS" :
return { ... state , products: action . payload , loading: false };
case "UPDATE_PROD_SUCCESS" :
return {
... state ,
loading: false ,
products: state . products . map (( p ) =>
p . id === action . payload . id ? action . payload : p
),
};
case "DELETE_PRODUCT" :
return {
... state ,
products: state . products . filter (( p ) => p . id !== action . payload ),
};
default :
return state ;
}
}
Sells Reducer
Manages sales transactions:
src/Redux/Reducer/sellsReducer.js
const initialState = {
sells: [],
currentSell: null ,
loading: false ,
error: null
};
export const sellsReducer = ( state = initialState , action ) => {
switch ( action . type ) {
case "SELLS_REQUEST" :
return { ... state , loading: true , error: null };
case "SELLS_SUCCESS" :
return { ... state , loading: false , sells: action . payload };
case "SELLS_CREATE_SUCCESS" :
return {
... state ,
loading: false ,
sells: [ action . payload , ... state . sells ]
};
case "SELL_CONFIRM_SUCCESS" :
return {
... state ,
sells: state . sells . map ( s =>
s . id === action . payload . id
? { ... s , status: "finalizado" , finishDate: new Date () }
: s
),
};
case "DELETE_SELL_SUCCESS" :
return {
... state ,
sells: state . sells . filter (( s ) => s . id !== action . payload ),
};
default :
return state ;
}
}
Dashboard Reducer
Stores analytics and business intelligence data:
src/Redux/Reducer/dashReducer.js
const initialState = {
loading: false ,
salesByDay: [],
salesByMonth: [],
topProducts: [],
salesByUser: [],
error: null ,
};
export function dashboardReducer ( state = initialState , action ) {
switch ( action . type ) {
case "DASHBOARD_REQUEST" :
return { ... state , loading: true , error: null };
case "DASHBOARD_SUCCESS" :
return { ... state , loading: false , ... action . payload };
case "DASHBOARD_FAILURE" :
return { ... state , loading: false , error: action . payload };
default :
return state ;
}
}
Action Creators with Redux Thunk
Async Action Pattern
All async actions follow a consistent three-phase pattern: REQUEST → SUCCESS/FAILURE
Login Action Example
src/Redux/actions/Auth/login.js
import { api } from "../../api" ;
export const LOGIN_REQUEST = 'LOGIN_REQUEST' ;
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS' ;
export const LOGIN_FAILURE = 'LOGIN_FAILURE' ;
export const LOGOUT = 'LOGOUT' ;
export const LoginUser = ( credentials , rememberMe ) => async ( dispatch ) => {
try {
dispatch ({ type: LOGIN_REQUEST });
const { data } = await api . post ( "/user/login" , credentials );
const storage = rememberMe ? localStorage : sessionStorage ;
storage . setItem ( "token" , data . token );
storage . setItem ( "user" , JSON . stringify ( data . userdata ));
dispatch ({
type: LOGIN_SUCCESS ,
payload: { token: data . token , user: data . userdata },
});
} catch ( error ) {
dispatch ({
type: LOGIN_FAILURE ,
payload: error . response ?. data ?. message || error . message ,
});
}
};
export const logout = () => ( dispatch ) => {
localStorage . removeItem ( 'token' );
localStorage . removeItem ( 'user' );
dispatch ({ type: LOGOUT });
};
Get Products Action
src/Redux/actions/Products/get_products.js
import { api } from "../../api" ;
export const getProducts = ( token ) => async ( dispatch ) => {
dispatch ({ type: 'GET_PROD_REQUEST' });
try {
const response = await api . get ( `/products` );
dispatch ({ type: 'GET_PROD_SUCCESS' , payload: response . data });
} catch ( error ) {
dispatch ({ type: 'GET_PROD_FAILURE' , payload: error . message });
}
};
Create Sell Action
src/Redux/actions/Sells/createSell.js
import { api } from "../../api" ;
export const createSell = ( products ) => async ( dispatch ) => {
dispatch ({ type: "CREATE_SELL_REQUEST" });
try {
const response = await api . post ( "/sells" , { products });
dispatch ({ type: "CREATE_SELL_SUCCESS" , payload: response . data });
return response . data ;
} catch ( error ) {
dispatch ({
type: "CREATE_SELL_FAILURE" ,
payload: error . response ?. data ?. error || "Error al crear venta" ,
});
throw error ;
}
};
Action Organization
Actions are organized by feature domain:
src/Redux/actions/
├── Auth/
│ ├── login.js
│ ├── create_user.js
│ ├── update_user.js
│ ├── forgotPassword.js
│ └── verify_resetToken.js
├── Products/
│ ├── get_products.js
│ ├── create_product.js
│ ├── edit_product.js
│ ├── delete_product.js
│ ├── get_categories.js
│ └── catalog_actions.js
├── Sells/
│ ├── createSell.js
│ ├── getUserSells.js
│ ├── confirmSell.js
│ └── deleteSell.js
└── Dash/
└── getDash.js
Using Redux in Components
Reading State with useSelector
import { useSelector } from 'react-redux' ;
function ProductList () {
const { products , loading , error } = useSelector ( state => state . products );
const token = useSelector ( state => state . auth . token );
if ( loading ) return < div > Loading... </ div > ;
if ( error ) return < div > Error: { error } </ div > ;
return (
< div >
{ products . map ( product => (
< ProductCard key = { product . id } product = { product } />
)) }
</ div >
);
}
Dispatching Actions with useDispatch
import { useDispatch } from 'react-redux' ;
import { LoginUser } from '../Redux/actions/Auth/login' ;
function LoginForm () {
const dispatch = useDispatch ();
const handleSubmit = async ( credentials , rememberMe ) => {
await dispatch ( LoginUser ( credentials , rememberMe ));
};
return < form onSubmit = { handleSubmit } > ... </ form > ;
}
State Management Best Practices
Always return new objects/arrays instead of mutating state directly. Use spread operators or array methods like map and filter.
Export action type constants to prevent typos and enable IDE autocomplete.
Every async action should handle both success and failure cases with appropriate error messages.
Track loading states to provide better UX with loading indicators.
Next Steps
API Integration Learn how Redux actions communicate with the backend API
Architecture Review the overall application architecture