Skip to main content

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:
src/Redux/store.js
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

Build docs developers (and LLMs) love