15 KiB
Custom Hooks Reference Guide
Complete documentation for all 8 custom hooks created in the refactoring.
Table of Contents
- useAuthForm
- usePasswordValidation
- useLoginLogic
- useRegisterLogic
- useHeaderLogic
- useResponsiveSidebar
- useProjectSidebarLogic
- useDashboardLogic
useAuthForm
Location: src/hooks/useAuthForm.ts
Lines: 55
Category: State Management
Used By: LoginPage, RegisterPage
Purpose
Centralized authentication form state and error management. Synchronizes local form state with Redux auth state.
Return Type
interface UseAuthFormReturn {
email: string;
password: string;
localError: string;
isLoading: boolean;
errorMessage: string | null;
setEmail: (email: string) => void;
setPassword: (password: string) => void;
setLocalError: (error: string) => void;
clearErrors: () => void;
}
Usage Example
const {
email,
password,
localError,
isLoading,
errorMessage,
setEmail,
setPassword,
clearErrors
} = useAuthForm();
return (
<form onSubmit={(e) => {
e.preventDefault();
clearErrors();
handleLogin({ email, password });
}}>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<input value={password} onChange={(e) => setPassword(e.target.value)} />
{(localError || errorMessage) && <div>{localError || errorMessage}</div>}
</form>
);
Key Features
- Syncs with Redux auth state (isLoading, errorMessage)
- Local error management
- Unified error clearing
- Simple API for form field updates
usePasswordValidation
Location: src/hooks/usePasswordValidation.ts
Lines: 52
Category: Business Logic
Used By: RegisterPage
Purpose
Calculate password strength and provide validation feedback.
Return Type
interface UsePasswordValidationReturn {
passwordStrength: number; // 0-4 scale
validatePassword: (pwd: string) => PasswordValidationResult;
handlePasswordChange: (value: string) => void;
}
interface PasswordValidationResult {
score: number; // 0-4
message: string; // "Weak", "Fair", "Good", "Strong"
}
Usage Example
const { passwordStrength, validatePassword, handlePasswordChange } = usePasswordValidation();
return (
<>
<input
type="password"
onChange={(e) => handlePasswordChange(e.target.value)}
/>
<div className="strength-bar">
<div style={{ width: `${(passwordStrength / 4) * 100}%` }} />
</div>
<span>{validatePassword(value).message}</span>
</>
);
Validation Rules
| Criterion | Points |
|---|---|
| Length ≥ 8 characters | 1 |
| Contains lowercase letters | 1 |
| Contains uppercase letters | 1 |
| Contains numbers | 1 |
| Total | 4 |
Strength Levels
| Score | Message |
|---|---|
| 0 | "Enter a password" |
| 1 | "Weak" |
| 2 | "Fair" |
| 3 | "Good" |
| 4 | "Strong" |
useLoginLogic
Location: src/hooks/useLoginLogic.ts
Lines: 68
Category: API Integration
Used By: LoginPage
Purpose
Complete login business logic including validation, API calls, state management, and navigation.
Return Type
interface UseLoginLogicReturn {
handleLogin: (data: LoginData) => Promise<void>;
}
interface LoginData {
email: string;
password: string;
}
Usage Example
const { handleLogin } = useLoginLogic();
const { email, password, clearErrors } = useAuthForm();
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();
clearErrors();
try {
await handleLogin({ email, password });
} catch (error) {
// Error already set in Redux state
}
};
return <form onSubmit={onSubmit}>...</form>;
Validation Rules
- Email must not be empty
- Password must not be empty
What It Does
- Validates form data
- Sets loading state (Redux)
- Calls
authService.login() - Saves token and user to localStorage
- Updates Redux auth state
- Redirects to dashboard (
/) - Handles errors and dispatches to Redux
Error Handling
try {
await handleLogin({ email, password });
} catch (error) {
// Error message is already in Redux state
// Error was also dispatched to Redux
// Display with: errorMessage from useAuthForm
}
useRegisterLogic
Location: src/hooks/useRegisterLogic.ts
Lines: 89
Category: API Integration
Used By: RegisterPage
Purpose
Registration business logic with comprehensive validation and API integration.
Return Type
interface UseRegisterLogicReturn {
handleRegister: (data: RegistrationData) => Promise<void>;
}
interface RegistrationData {
name: string;
email: string;
password: string;
confirmPassword: string;
}
Usage Example
const { handleRegister } = useRegisterLogic();
const { email, password, clearErrors } = useAuthForm();
const [name, setName] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();
clearErrors();
try {
await handleRegister({
name,
email,
password,
confirmPassword
});
} catch (error) {
// Error is in Redux state
}
};
Validation Rules
| Field | Rules |
|---|---|
| Name | Required, min 2 characters |
| Required, non-empty | |
| Password | Required, min 8 chars, uppercase, lowercase, numbers |
| Confirm | Must match password |
What It Does
- Validates all form fields
- Sets loading state (Redux)
- Calls
authService.register() - Saves token and user to localStorage
- Updates Redux auth state
- Redirects to dashboard (
/) - Handles errors and dispatches to Redux
useHeaderLogic
Location: src/hooks/useHeaderLogic.ts
Lines: 48
Category: UI Logic
Used By: MainLayout Header component
Purpose
Header component logic for user menu management and logout functionality.
Return Type
interface UseHeaderLogicReturn {
user: any; // Current user object
isAuthenticated: boolean; // Auth status
showUserMenu: boolean; // Menu visibility
setShowUserMenu: (show: boolean) => void;
handleLogout: () => void; // Execute logout
toggleUserMenu: () => void; // Toggle menu
}
Usage Example
const {
user,
isAuthenticated,
showUserMenu,
handleLogout,
toggleUserMenu
} = useHeaderLogic();
return (
<header>
{isAuthenticated && user && (
<div>
<button onClick={toggleUserMenu}>
{user.name}
</button>
{showUserMenu && (
<menu>
<span>{user.email}</span>
<button onClick={handleLogout}>Logout</button>
</menu>
)}
</div>
)}
</header>
);
What It Does
- Manages user menu visibility state
- Clears localStorage on logout
- Dispatches logout action to Redux
- Closes menu after logout
- Redirects to login page
useResponsiveSidebar
Location: src/hooks/useResponsiveSidebar.ts
Lines: 50
Category: Responsive Logic
Used By: MainLayout
Purpose
Responsive sidebar behavior with mobile detection and auto-close functionality.
Parameters
useResponsiveSidebar(
sidebarOpen: boolean,
onSidebarChange: (open: boolean) => void
)
Return Type
interface UseResponsiveSidebarReturn {
isMobile: boolean; // Is mobile screen
isCollapsed: boolean; // Collapse state
setIsCollapsed: (collapsed: boolean) => void;
toggleCollapsed: () => void;
}
Usage Example
const { isMobile, isCollapsed, toggleCollapsed } = useResponsiveSidebar(
sidebarOpen,
setSidebar
);
return (
<div>
<header>
<button onClick={toggleCollapsed}>
{isCollapsed ? 'Expand' : 'Collapse'}
</button>
</header>
{!isCollapsed && (
<aside>{/* Sidebar content */}</aside>
)}
</div>
);
Mobile Breakpoint
- Mobile:
window.innerWidth < 768px - Desktop:
window.innerWidth >= 768px
Features
- Window resize listener with cleanup
- Auto-closes sidebar on mobile
- Collapse/expand toggle
- Performance optimized with useCallback
useProjectSidebarLogic
Location: src/hooks/useProjectSidebarLogic.ts
Lines: 91
Category: Data Management
Used By: ProjectSidebar
Purpose
Project sidebar logic with filtering, form management, and optimized performance.
Parameters
useProjectSidebarLogic(projects: Project[])
Return Type
interface UseProjectSidebarLogicReturn {
isCollapsed: boolean;
showNewProjectForm: boolean;
newProjectName: string;
starredProjects: Project[]; // Memoized
regularProjects: Project[]; // Memoized
setIsCollapsed: (collapsed: boolean) => void;
toggleCollapsed: () => void;
setShowNewProjectForm: (show: boolean) => void;
setNewProjectName: (name: string) => void;
handleCreateProject: (e: React.FormEvent, onSuccess: () => void) => Promise<void>;
handleProjectClick: (projectId: string, onSelect: (id: string) => void) => void;
resetProjectForm: () => void;
}
Usage Example
const {
starredProjects,
regularProjects,
showNewProjectForm,
newProjectName,
handleCreateProject,
handleProjectClick,
resetProjectForm
} = useProjectSidebarLogic(projects);
return (
<aside>
{starredProjects.map(p => (
<div
key={p.id}
onClick={() => handleProjectClick(p.id, onSelectProject)}
>
{p.name}
</div>
))}
{showNewProjectForm && (
<form onSubmit={(e) => handleCreateProject(e, () => Promise.resolve())}>
<input value={newProjectName} onChange={...} />
<button onClick={resetProjectForm}>Cancel</button>
</form>
)}
</aside>
);
Performance Optimizations
- Memoized filtering with useMemo
- Callback optimization with useCallback
- Prevents unnecessary re-renders
useDashboardLogic
Location: src/hooks/useDashboardLogic.ts
Lines: 84
Category: Data Management
Used By: Dashboard (app/page.tsx)
Purpose
Dashboard workspace management including creation, switching, and loading states.
Return Type
interface UseDashboardLogicReturn {
isLoading: boolean;
showCreateForm: boolean;
newWorkspaceName: string;
workspaces: any[];
currentWorkspace: any;
setShowCreateForm: (show: boolean) => void;
setNewWorkspaceName: (name: string) => void;
handleCreateWorkspace: (e: React.FormEvent) => Promise<void>;
handleWorkspaceClick: (workspaceId: string) => void;
resetWorkspaceForm: () => void;
}
Usage Example
const {
isLoading,
workspaces,
showCreateForm,
newWorkspaceName,
handleCreateWorkspace,
handleWorkspaceClick,
resetWorkspaceForm,
setShowCreateForm,
setNewWorkspaceName
} = useDashboardLogic();
return (
<div>
{isLoading ? (
<div>Loading...</div>
) : (
<>
{showCreateForm && (
<form onSubmit={handleCreateWorkspace}>
<input
value={newWorkspaceName}
onChange={(e) => setNewWorkspaceName(e.target.value)}
/>
<button onClick={resetWorkspaceForm}>Cancel</button>
</form>
)}
<div>
{workspaces.map(w => (
<div
key={w.id}
onClick={() => handleWorkspaceClick(w.id)}
>
{w.name}
</div>
))}
</div>
</>
)}
</div>
);
What It Does
- Loads workspaces on mount (useEffect)
- Sets loading state during operations
- Creates new workspace with default color (#1976d2)
- Switches to new workspace
- Navigates to workspace page
- Handles form state and reset
Default Workspace Config
{
name: newWorkspaceName,
description: '',
color: '#1976d2'
}
Best Practices
Composition Pattern
Combine multiple hooks in a component:
export default function RegisterPage() {
const { email, password, ...authForm } = useAuthForm();
const { passwordStrength, ...validation } = usePasswordValidation();
const { handleRegister } = useRegisterLogic();
// ... component logic
}
Error Handling
All hooks that make API calls handle errors:
try {
await hookFunction();
} catch (error) {
// Error is already in Redux state
// Display with selector from errorMessage
}
Callback Dependencies
All callbacks are properly memoized:
const handleClick = useCallback(() => {
// handler logic
}, [dependencies]); // Explicit dependencies
Form Reset Pattern
Standard form reset across all hooks:
const resetForm = useCallback(() => {
setFormField('');
setShowForm(false);
}, []);
Testing Examples
Testing usePasswordValidation
it('should validate strong password', () => {
const validation = usePasswordValidation()
.validatePassword('Abc123!@');
expect(validation.score).toBe(4);
expect(validation.message).toBe('Strong');
});
Testing useLoginLogic
it('should reject empty email', async () => {
const { handleLogin } = useLoginLogic();
await expect(
handleLogin({ email: '', password: 'pass' })
).rejects.toThrow('Email is required');
});
Testing useProjectSidebarLogic
it('should filter starred projects', () => {
const projects = [
{ id: 1, starred: true },
{ id: 2, starred: false }
];
const { starredProjects } = useProjectSidebarLogic(projects);
expect(starredProjects).toHaveLength(1);
});
Migration Guide
Before (Mixed Logic & UI)
export default function LoginPage() {
const [email, setEmail] = useState('');
const dispatch = useDispatch();
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
dispatch(setLoading(true));
try {
const response = await authService.login(email, password);
localStorage.setItem('auth_token', response.token);
dispatch(setAuthenticated(response));
router.push('/');
} catch (error) {
dispatch(setError(error.message));
}
};
return <form onSubmit={handleLogin}>...</form>;
}
After (Separated Logic & UI)
export default function LoginPage() {
const { email, setEmail, clearErrors } = useAuthForm();
const { handleLogin } = useLoginLogic();
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();
clearErrors();
try {
await handleLogin({ email, password });
} catch {
// Error in Redux state
}
};
return <form onSubmit={onSubmit}>...</form>;
}
Summary
| Hook | LOC | Category | Components |
|---|---|---|---|
| useAuthForm | 55 | State | Login, Register |
| usePasswordValidation | 52 | Logic | Register |
| useLoginLogic | 68 | API | Login |
| useRegisterLogic | 89 | API | Register |
| useHeaderLogic | 48 | UI | MainLayout |
| useResponsiveSidebar | 50 | Responsive | MainLayout |
| useProjectSidebarLogic | 91 | Data | ProjectSidebar |
| useDashboardLogic | 84 | Data | Dashboard |
| Total | 534 | — | — |
All hooks are production-ready and type-safe with full TypeScript support.