# Example Usage of New Type Patterns This document shows practical examples of how to use the new unified type patterns from `@customer-portal/domain`. ## 1. Basic Async State Usage ### Custom Hook with New Async State ```typescript // hooks/useUser.ts import { useState, useEffect } from 'react'; import { AsyncState, createIdleState, createLoadingState, createSuccessState, createErrorState, type User } from '@customer-portal/domain'; export function useUser(userId: string) { const [state, setState] = useState>(createIdleState()); useEffect(() => { if (!userId) return; setState(createLoadingState()); fetchUser(userId) .then(user => setState(createSuccessState(user))) .catch(error => setState(createErrorState(error.message))); }, [userId]); return state; } ``` ### Component Using Async State ```typescript // components/UserProfile.tsx import React from 'react'; import { isLoading, isError, isSuccess, isIdle } from '@customer-portal/domain'; import { useUser } from '../hooks/useUser'; interface UserProfileProps { userId: string; } export function UserProfile({ userId }: UserProfileProps) { const userState = useUser(userId); if (isIdle(userState)) { return
Ready to load user...
; } if (isLoading(userState)) { return
Loading user...
; } if (isError(userState)) { return (

Error loading user

{userState.error}

); } if (isSuccess(userState)) { return (

{userState.data.name}

Email: {userState.data.email}

Phone: {userState.data.phone}

); } // This should never happen with discriminated unions return null; } ``` ## 2. Form State Usage ### Custom Form Hook ```typescript // hooks/useLoginForm.ts import { useState } from 'react'; import { FormState, createFormState, updateFormField, getFormValues, hasFormErrors, isFormDirty } from '@customer-portal/domain'; interface LoginFormData { email: string; password: string; } export function useLoginForm() { const [formState, setFormState] = useState>( createFormState({ email: '', password: '', }) ); const updateField = (field: keyof LoginFormData, value: string, error?: string) => { setFormState(prev => ({ ...prev, [field]: updateFormField(prev[field], value, error), isValid: !error && !hasFormErrors(prev), })); }; const validateEmail = (email: string): string | undefined => { if (!email) return 'Email is required'; if (!/\S+@\S+\.\S+/.test(email)) return 'Email is invalid'; return undefined; }; const validatePassword = (password: string): string | undefined => { if (!password) return 'Password is required'; if (password.length < 8) return 'Password must be at least 8 characters'; return undefined; }; const handleEmailChange = (email: string) => { const error = validateEmail(email); updateField('email', email, error); }; const handlePasswordChange = (password: string) => { const error = validatePassword(password); updateField('password', password, error); }; const handleSubmit = async () => { const formData = getFormValues(formState); // Validate all fields const emailError = validateEmail(formData.email); const passwordError = validatePassword(formData.password); if (emailError || passwordError) { setFormState(prev => ({ ...prev, email: { ...prev.email, error: emailError }, password: { ...prev.password, error: passwordError }, isValid: false, })); return; } setFormState(prev => ({ ...prev, isSubmitting: true })); try { await login(formData); // Handle success } catch (error) { // Handle error setFormState(prev => ({ ...prev, isSubmitting: false, errors: { email: 'Login failed. Please check your credentials.' } })); } }; return { formState, handleEmailChange, handlePasswordChange, handleSubmit, isValid: formState.isValid && !hasFormErrors(formState), isDirty: isFormDirty(formState), }; } ``` ### Login Form Component ```typescript // components/LoginForm.tsx import React from 'react'; import { useLoginForm } from '../hooks/useLoginForm'; export function LoginForm() { const { formState, handleEmailChange, handlePasswordChange, handleSubmit, isValid, isDirty, } = useLoginForm(); return (
{ e.preventDefault(); handleSubmit(); }}>
handleEmailChange(e.target.value)} className={formState.email.error ? 'error' : ''} /> {formState.email.error && ( {formState.email.error} )}
handlePasswordChange(e.target.value)} className={formState.password.error ? 'error' : ''} /> {formState.password.error && ( {formState.password.error} )}
{isDirty && (

You have unsaved changes

)}
); } ``` ## 3. API Integration with Adapters ### Service Layer with Adapters ```typescript // services/userService.ts import { adaptWhmcsResponse, adaptSalesforceResponse, unwrapResponse, isSuccessResponse, type ApiResponse, type User, type WhmcsClientId, createWhmcsClientId } from '@customer-portal/domain'; class UserService { async getUser(userId: string): Promise { // Get user from WHMCS const whmcsResponse = await this.whmcsApi.getClient( createWhmcsClientId(parseInt(userId)) ); const unifiedResponse = adaptWhmcsResponse(whmcsResponse); return unwrapResponse(unifiedResponse); } async updateUser(userId: string, userData: Partial): Promise { // Update in both WHMCS and Salesforce const [whmcsResponse, sfResponse] = await Promise.all([ this.whmcsApi.updateClient(createWhmcsClientId(parseInt(userId)), userData), this.salesforceApi.updateContact(userId, userData), ]); const whmcsResult = adaptWhmcsResponse(whmcsResponse); const sfResult = adaptSalesforceResponse(sfResponse); // Check both responses if (!isSuccessResponse(whmcsResult)) { throw new Error(`WHMCS update failed: ${whmcsResult.error.message}`); } if (!isSuccessResponse(sfResult)) { throw new Error(`Salesforce update failed: ${sfResult.error.message}`); } return whmcsResult.data; } async searchUsers(query: string): Promise { try { const response = await this.api.searchUsers(query); const adaptedResponse = adaptWhmcsResponse(response); return unwrapResponse(adaptedResponse); } catch (error) { console.error('User search failed:', error); return []; } } } export const userService = new UserService(); ``` ### Hook with API Integration ```typescript // hooks/useUserSearch.ts import { useState, useCallback } from 'react'; import { AsyncState, createIdleState, createLoadingState, createSuccessState, createErrorState, type User } from '@customer-portal/domain'; import { userService } from '../services/userService'; export function useUserSearch() { const [searchState, setSearchState] = useState>(createIdleState()); const [query, setQuery] = useState(''); const searchUsers = useCallback(async (searchQuery: string) => { if (!searchQuery.trim()) { setSearchState(createIdleState()); return; } setSearchState(createLoadingState()); try { const users = await userService.searchUsers(searchQuery); setSearchState(createSuccessState(users)); } catch (error) { setSearchState(createErrorState(error instanceof Error ? error.message : 'Search failed')); } }, []); return { searchState, query, setQuery, searchUsers, }; } ``` ## 4. Paginated Data Usage ### Paginated List Hook ```typescript // hooks/useUserList.ts import { useState, useEffect, useCallback } from 'react'; import { PaginatedAsyncState, PaginationParams, createIdleState, createLoadingState, createSuccessState, createErrorState, validatePaginationParams, type User } from '@customer-portal/domain'; export function useUserList(initialParams: PaginationParams = {}) { const [state, setState] = useState>(createIdleState()); const [params, setParams] = useState(() => validatePaginationParams(initialParams)); const loadUsers = useCallback(async (paginationParams: PaginationParams) => { setState(createLoadingState()); try { const validatedParams = validatePaginationParams(paginationParams); const response = await userService.getUsers(validatedParams); setState({ status: 'success', data: response.data, pagination: response.pagination, }); } catch (error) { setState(createErrorState(error instanceof Error ? error.message : 'Failed to load users')); } }, []); const goToPage = useCallback((page: number) => { const newParams = { ...params, page }; setParams(newParams); loadUsers(newParams); }, [params, loadUsers]); const changePageSize = useCallback((limit: number) => { const newParams = { ...params, limit, page: 1 }; setParams(newParams); loadUsers(newParams); }, [params, loadUsers]); useEffect(() => { loadUsers(params); }, [loadUsers, params]); return { state, params, goToPage, changePageSize, reload: () => loadUsers(params), }; } ``` ### Paginated List Component ```typescript // components/UserList.tsx import React from 'react'; import { isLoading, isError, isSuccess } from '@customer-portal/domain'; import { useUserList } from '../hooks/useUserList'; export function UserList() { const { state, params, goToPage, changePageSize, reload } = useUserList({ page: 1, limit: 10, }); if (isLoading(state)) { return
Loading users...
; } if (isError(state)) { return (

Error: {state.error}

); } if (isSuccess(state)) { return (

Users ({state.pagination.total})

{state.data.map(user => (

{user.name}

{user.email}

))}
Page {state.pagination.page} of {state.pagination.totalPages}
); } return null; } ``` ## 5. Selection State Usage ### Selection Hook ```typescript // hooks/useSelection.ts import { useState, useCallback } from 'react'; import { SelectionState, createSelectionState, updateSelectionState } from '@customer-portal/domain'; export function useSelection() { const [selectionState, setSelectionState] = useState>( createSelectionState() ); const toggleItem = useCallback((item: T) => { setSelectionState(prev => { const isSelected = prev.selected.includes(item); return updateSelectionState(prev, item, !isSelected); }); }, []); const selectAll = useCallback((items: T[]) => { setSelectionState({ selected: [...items], selectAll: true, indeterminate: false, }); }, []); const deselectAll = useCallback(() => { setSelectionState(createSelectionState()); }, []); return { selectionState, toggleItem, selectAll, deselectAll, hasSelection: selectionState.selected.length > 0, selectedCount: selectionState.selected.length, }; } ``` ## Key Benefits Demonstrated 1. **Type Safety**: Impossible states are eliminated 2. **Consistency**: Same patterns across all components 3. **Reusability**: Hooks and utilities can be shared 4. **Maintainability**: Clear separation of concerns 5. **Developer Experience**: Better IntelliSense and debugging ## Next Steps 1. Use these patterns in your new components 2. Gradually migrate existing components 3. Add tests for your hooks and components 4. Customize the patterns for your specific needs These examples show how the new type patterns lead to more robust, maintainable, and type-safe code while providing an excellent developer experience.