Skip to main content

State Management Taxonomy

BonardaHR follows a strict State Categorization Strategy to keep the codebase maintainable and avoid monolithic state stores.


State Categories​

Type of StateLibrary / MechanismExamplesWhy This Choice?
Server StateTanStack React QueryEmployees, Timesheets, Balances, DocumentsBuilt-in caching, background revalidation, query deduplication, optimistic updates
Authentication StateReact Context (AuthProvider)Token, user profile, role permissionsApplication-wide, updated rarely, drives route guards
Impersonation StateReact Context (ImpersonationProvider)Impersonated target employee, audit modePersists across page refresh via sessionStorage, affects all API calls
Form StateReact Hook Form + ZodEmployee Wizard, Time Off Request, PoliciesHigh performance, avoids re-renders on keystrokes, co-located schema validation
Local UI StateReact useState / useReducerModal open/close, active tabs, dropdown openStrictly component-scoped, no cross-component sharing required
URL Search StateReact Router useSearchParamsFilter tabs, pagination page, search keywordsShareable, 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,
},
},
});