State Management Taxonomy
BonardaHR follows a strict State Categorization Strategy to keep the codebase maintainable and avoid monolithic state stores.
State Categories​
| Type of State | Library / Mechanism | Examples | Why This Choice? |
|---|---|---|---|
| Server State | TanStack React Query | Employees, Timesheets, Balances, Documents | Built-in caching, background revalidation, query deduplication, optimistic updates |
| Authentication State | React Context (AuthProvider) | Token, user profile, role permissions | Application-wide, updated rarely, drives route guards |
| Impersonation State | React Context (ImpersonationProvider) | Impersonated target employee, audit mode | Persists across page refresh via sessionStorage, affects all API calls |
| Form State | React Hook Form + Zod | Employee Wizard, Time Off Request, Policies | High performance, avoids re-renders on keystrokes, co-located schema validation |
| Local UI State | React useState / useReducer | Modal open/close, active tabs, dropdown open | Strictly component-scoped, no cross-component sharing required |
| URL Search State | React Router useSearchParams | Filter tabs, pagination page, search keywords | Shareable, bookmarkable, preserves browser back/forward history |
React Query Best Practices in BonardaHR​
1. Consistent Query Key Factories​
Always declare structured query keys to avoid collisions and facilitate targeted cache invalidation:
// features/employees/hooks/useEmployees.ts
export const employeeKeys = {
all: ['employees'] as const,
lists: () => [...employeeKeys.all, 'list'] as const,
list: (filters: EmployeeFilters) => [...employeeKeys.lists(), filters] as const,
details: () => [...employeeKeys.all, 'detail'] as const,
detail: (id: string) => [...employeeKeys.details(), id] as const,
hierarchy: (id: string) => [...employeeKeys.all, 'hierarchy', id] as const,
};
2. Custom Mutation Hooks with Cache Invalidation​
Mutations encapsulate optimistic updates and cache invalidation within their feature hook:
export function useCreateEmployee() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateEmployeeDTO) => employeeService.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: employeeKeys.lists() });
},
});
}
3. Query Defaults​
Default query options in App.tsx prevent unnecessary refetches:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes fresh
gcTime: 1000 * 60 * 30, // 30 minutes in memory
retry: 1,
refetchOnWindowFocus: false,
},
},
});