Files
workforce-pay-bill-p/REDUX_GUIDE.md

8.5 KiB

Redux Integration Guide

This application uses Redux Toolkit for centralized state management across the entire platform.

Architecture Overview

The Redux store is configured in /src/store/store.ts and provides the following slices:

State Slices

1. Auth Slice (/src/store/slices/authSlice.ts)

Manages authentication state and user information.

State:

  • user: Current authenticated user object
  • isAuthenticated: Boolean flag for auth status
  • currentEntity: Selected organizational entity

Actions:

  • login(user): Authenticates user and stores user data
  • logout(): Clears user session
  • setCurrentEntity(entity): Switches between organizational entities

2. UI Slice (/src/store/slices/uiSlice.ts)

Manages global UI state.

State:

  • currentView: Active view/page in the application
  • searchQuery: Global search query string
  • sidebarCollapsed: Sidebar visibility state

Actions:

  • setCurrentView(view): Navigate to different views
  • setSearchQuery(query): Update search query
  • toggleSidebar(): Toggle sidebar collapsed state

3. Timesheets Slice (/src/store/slices/timesheetsSlice.ts)

Manages timesheet data.

State:

  • timesheets: Array of timesheet objects
  • loading: Loading state for async operations

Actions:

  • setTimesheets(timesheets): Replace all timesheets
  • addTimesheet(timesheet): Add new timesheet
  • updateTimesheet(timesheet): Update existing timesheet
  • deleteTimesheet(id): Remove timesheet by ID
  • setLoading(boolean): Set loading state

4. Invoices Slice (/src/store/slices/invoicesSlice.ts)

Manages invoice data.

State:

  • invoices: Array of invoice objects
  • loading: Loading state for async operations

Actions:

  • setInvoices(invoices): Replace all invoices
  • addInvoice(invoice): Add new invoice
  • updateInvoice(invoice): Update existing invoice
  • deleteInvoice(id): Remove invoice by ID
  • setLoading(boolean): Set loading state

5. Payroll Slice (/src/store/slices/payrollSlice.ts)

Manages payroll run data.

State:

  • payrollRuns: Array of payroll run objects
  • loading: Loading state for async operations

Actions:

  • setPayrollRuns(runs): Replace all payroll runs
  • addPayrollRun(run): Add new payroll run
  • updatePayrollRun(run): Update existing payroll run
  • setLoading(boolean): Set loading state

6. Compliance Slice (/src/store/slices/complianceSlice.ts)

Manages compliance document data.

State:

  • documents: Array of compliance document objects
  • loading: Loading state for async operations

Actions:

  • setComplianceDocs(docs): Replace all compliance documents
  • addComplianceDoc(doc): Add new compliance document
  • updateComplianceDoc(doc): Update existing compliance document
  • deleteComplianceDoc(id): Remove compliance document by ID
  • setLoading(boolean): Set loading state

7. Expenses Slice (/src/store/slices/expensesSlice.ts)

Manages expense data.

State:

  • expenses: Array of expense objects
  • loading: Loading state for async operations

Actions:

  • setExpenses(expenses): Replace all expenses
  • addExpense(expense): Add new expense
  • updateExpense(expense): Update existing expense
  • deleteExpense(id): Remove expense by ID
  • setLoading(boolean): Set loading state

8. Notifications Slice (/src/store/slices/notificationsSlice.ts)

Manages system notifications.

State:

  • notifications: Array of notification objects
  • unreadCount: Number of unread notifications

Actions:

  • setNotifications(notifications): Replace all notifications
  • addNotification(notification): Add new notification
  • markAsRead(id): Mark notification as read
  • markAllAsRead(): Mark all notifications as read
  • deleteNotification(id): Remove notification by ID

Using Redux in Components

1. Import the typed hooks

import { useAppSelector, useAppDispatch } from '@/store/hooks'

2. Access state with useAppSelector

const user = useAppSelector(state => state.auth.user)
const isAuthenticated = useAppSelector(state => state.auth.isAuthenticated)
const timesheets = useAppSelector(state => state.timesheets.timesheets)

3. Dispatch actions with useAppDispatch

import { login, logout } from '@/store/slices/authSlice'
import { setCurrentView } from '@/store/slices/uiSlice'

const dispatch = useAppDispatch()

// Dispatch actions
dispatch(login({ id: '1', email: 'user@example.com', name: 'User', role: 'Admin' }))
dispatch(setCurrentView('dashboard'))
dispatch(logout())

Custom Hooks with Redux

We've created several custom hooks that wrap Redux logic for cleaner component code:

useAuth Hook (/src/hooks/use-auth.ts)

import { useAuth } from '@/hooks/use-auth'

function MyComponent() {
  const { user, isAuthenticated, currentEntity, logout } = useAuth()
  
  return (
    <div>
      {isAuthenticated && <p>Welcome, {user?.name}!</p>}
      <button onClick={logout}>Log Out</button>
    </div>
  )
}

useNavigation Hook (/src/hooks/use-navigation.ts)

import { useNavigation } from '@/hooks/use-navigation'

function MyComponent() {
  const { currentView, navigateTo } = useNavigation()
  
  return (
    <button onClick={() => navigateTo('dashboard')}>
      Go to Dashboard
    </button>
  )
}

useReduxNotifications Hook (/src/hooks/use-redux-notifications.ts)

import { useReduxNotifications } from '@/hooks/use-redux-notifications'

function MyComponent() {
  const { notifications, unreadCount, addNotification, markAsRead } = useReduxNotifications()
  
  const handleAction = () => {
    addNotification({
      type: 'success',
      title: 'Success',
      message: 'Action completed successfully',
    })
  }
  
  return (
    <div>
      <p>Unread: {unreadCount}</p>
      {notifications.map(n => (
        <div key={n.id} onClick={() => markAsRead(n.id)}>
          {n.title}
        </div>
      ))}
    </div>
  )
}

Best Practices

  1. Use typed hooks: Always use useAppSelector and useAppDispatch instead of the untyped versions from react-redux

  2. Create custom hooks: Wrap common Redux patterns in custom hooks for reusability and cleaner components

  3. Keep slices focused: Each slice should manage a single domain of state

  4. Use Redux DevTools: The store is configured with Redux DevTools support for debugging

  5. Immutable updates: Redux Toolkit uses Immer internally, so you can write "mutating" code in reducers that is automatically converted to immutable updates

  6. Async operations: For async operations, consider using Redux Toolkit's createAsyncThunk or handle them in components/custom hooks

Example: Complete Component with Redux

import { useAppSelector, useAppDispatch } from '@/store/hooks'
import { setTimesheets, addTimesheet } from '@/store/slices/timesheetsSlice'
import { addNotification } from '@/store/slices/notificationsSlice'
import { Button } from '@/components/ui/button'

export function TimesheetList() {
  const dispatch = useAppDispatch()
  const timesheets = useAppSelector(state => state.timesheets.timesheets)
  const loading = useAppSelector(state => state.timesheets.loading)
  
  const handleAddTimesheet = () => {
    const newTimesheet = {
      id: `TS-${Date.now()}`,
      workerName: 'New Worker',
      status: 'pending',
      // ... other fields
    }
    
    dispatch(addTimesheet(newTimesheet))
    dispatch(addNotification({
      id: `notif-${Date.now()}`,
      type: 'success',
      title: 'Timesheet Added',
      message: 'New timesheet created successfully',
      timestamp: new Date().toISOString(),
      read: false,
    }))
  }
  
  if (loading) return <div>Loading...</div>
  
  return (
    <div>
      <Button onClick={handleAddTimesheet}>Add Timesheet</Button>
      {timesheets.map(ts => (
        <div key={ts.id}>{ts.workerName}</div>
      ))}
    </div>
  )
}

Migration from useState to Redux

For existing components using local state, follow this pattern:

Before (local state):

const [currentView, setCurrentView] = useState('dashboard')

After (Redux):

const currentView = useAppSelector(state => state.ui.currentView)
const dispatch = useAppDispatch()

// To update:
dispatch(setCurrentView('timesheets'))

Future Enhancements

Consider adding these Redux features as the application grows:

  • Redux Persist: Persist state to localStorage/sessionStorage
  • RTK Query: For API data fetching and caching
  • createAsyncThunk: For complex async operations
  • Entity Adapters: For normalized state management of collections