Overview
The useCVVersions hook provides complete version control functionality including creating, listing, restoring, updating, and deleting CV versions. It includes pagination, search, and optimistic UI updates.
Import
import { useCVVersions } from '@/hooks/useCVVersions';
Usage
const VersionHistory = () => {
const { user } = useAuth();
const {
versions,
isLoading,
hasMore,
saveNewVersion,
restoreToVersion,
deleteVersionById,
updateVersion,
loadMore,
searchVersionsList
} = useCVVersions({
userId: user?.uid ?? null
});
const handleSave = async () => {
const versionId = await saveNewVersion({
versionName: 'Senior Developer Resume',
description: 'Updated for tech company applications',
tags: ['tech', 'senior', '2024']
});
if (versionId) {
toast.success('Version saved!');
}
};
const handleRestore = async (versionId: string) => {
const success = await restoreToVersion(versionId);
if (success) {
toast.success('Version restored');
window.location.reload(); // Reload to show restored data
}
};
return (
<div>
<SearchBar onSearch={searchVersionsList} />
<VersionList versions={versions} onRestore={handleRestore} />
{hasMore && <LoadMoreButton onClick={loadMore} />}
</div>
);
};
Parameters
User ID to fetch versions for. Pass null for unauthenticated users (no versions will load).
Return Value
State
Array of version metadata objects, sorted by creation date (newest first).Structure:interface CVVersionMetadata {
id: string;
userId: string;
versionName: string;
description?: string;
tags?: string[];
createdAt: Date;
isAutoSave: boolean;
}
true while loading initial versions or performing search.
true while loading additional pages via pagination.
true if more versions are available to load. false during search mode.
Error message if version operations fail. null when no error.
true while saving a new version.
true while restoring a version.
ID of version currently being deleted. null when no deletion in progress.
Actions
Reload versions list from the beginning. Resets pagination.Use cases:
- After creating a version externally
- Manual refresh button
- Clearing filters
Load next page of versions (20 per page).Behavior:
- Disabled during search mode
- Does nothing if
hasMore is false
- Appends results to existing
versions array
saveNewVersion
(input: CVVersionInput) => Promise<string | null>
Save current form data as a new version.Parameters:interface CVVersionInput {
versionName: string;
description?: string;
tags?: string[];
}
Returns: Version ID on success, null on failureSide effects:
- Optimistically adds version to list
- Uses current form data via
getValues()
getVersionData
(versionId: string) => Promise<CVVersion | null>
Fetch complete version data including CV content.Returns:interface CVVersion {
id: string;
userId: string;
data: CVData; // Full CV content
versionName: string;
description?: string;
tags?: string[];
createdAt: Date;
isAutoSave: boolean;
}
Use cases:
- Preview version before restoring
- Compare versions
- Export version data
restoreToVersion
(versionId: string) => Promise<boolean>
Restore CV to a specific version.Returns: true on success, false on failureBehavior:
- Fetches version data
- Updates main CV document
- Does NOT update form state (requires page reload)
Important: Call window.location.reload() or re-fetch CV data after restoring.
deleteVersionById
(versionId: string) => Promise<boolean>
Delete a version permanently.Returns: true on success, false on failureSide effects:
- Optimistically removes from list
- Updates
isDeleting state during operation
updateVersion
(versionId: string, metadata: Partial<CVVersionInput>) => Promise<boolean>
Update version metadata (name, description, tags).Parameters:metadata: {
versionName?: string;
description?: string;
tags?: string[];
}
Returns: true on success, false on failureSide effects:
- Optimistically updates list
- Does not modify CV data, only metadata
searchVersionsList
(searchTerm: string) => Promise<void>
Search versions by name, description, or tags.Behavior:
- Replaces current versions list with search results
- Disables pagination (
hasMore becomes false)
- Use
clearSearch() to restore full list
Clear search and reload all versions.Equivalent to: refreshVersions()
Features
Loads 20 versions per page:
const PAGE_SIZE = 20;
const VersionList = () => {
const { versions, hasMore, isLoadingMore, loadMore } = useCVVersions({ userId });
return (
<div>
{versions.map(v => <VersionCard key={v.id} version={v} />)}
{hasMore && (
<Button onClick={loadMore} disabled={isLoadingMore}>
{isLoadingMore ? 'Loading...' : 'Load More'}
</Button>
)}
</div>
);
};
Optimistic UI Updates
Updates UI immediately before server confirms:
// Save: Adds to list immediately
const versionId = await saveNewVersion(input);
// Version appears in list right away
// Delete: Removes from list immediately
const success = await deleteVersionById(versionId);
// Version disappears right away
// Update: Changes metadata immediately
const success = await updateVersion(versionId, { versionName: 'New Name' });
// Name changes right away
Search Mode
Disables pagination during search:
const SearchableVersions = () => {
const [query, setQuery] = useState('');
const { versions, searchVersionsList, clearSearch, hasMore } = useCVVersions({ userId });
const handleSearch = async (term: string) => {
setQuery(term);
if (term) {
await searchVersionsList(term);
} else {
await clearSearch();
}
};
return (
<div>
<SearchInput value={query} onChange={handleSearch} />
<VersionList versions={versions} />
{/* Load More button hidden during search (hasMore = false) */}
</div>
);
};
Advanced Examples
Version Comparison
const VersionComparison = ({ versionId1, versionId2 }) => {
const { getVersionData } = useCVVersions({ userId });
const [versions, setVersions] = useState<[CVVersion, CVVersion] | null>(null);
useEffect(() => {
const load = async () => {
const [v1, v2] = await Promise.all([
getVersionData(versionId1),
getVersionData(versionId2)
]);
if (v1 && v2) setVersions([v1, v2]);
};
load();
}, [versionId1, versionId2]);
if (!versions) return <Loading />;
return (
<div className="grid grid-cols-2 gap-4">
<VersionPreview data={versions[0].data} />
<VersionPreview data={versions[1].data} />
</div>
);
};
Bulk Operations
const BulkDelete = ({ versionIds }: { versionIds: string[] }) => {
const { deleteVersionById } = useCVVersions({ userId });
const [progress, setProgress] = useState(0);
const handleBulkDelete = async () => {
for (let i = 0; i < versionIds.length; i++) {
await deleteVersionById(versionIds[i]);
setProgress(((i + 1) / versionIds.length) * 100);
}
toast.success(`Deleted ${versionIds.length} versions`);
};
return (
<div>
<Button onClick={handleBulkDelete}>Delete Selected</Button>
<Progress value={progress} />
</div>
);
};
Auto-Save Versions
const AutoSaveManager = () => {
const { saveNewVersion } = useCVVersions({ userId });
const form = useFormContext<CVData>();
useEffect(() => {
const interval = setInterval(async () => {
if (form.formState.isDirty) {
await saveNewVersion({
versionName: `Auto-save ${new Date().toLocaleString()}`,
description: 'Automatic backup',
tags: ['auto-save']
});
console.log('Auto-save complete');
}
}, 60000); // Every minute
return () => clearInterval(interval);
}, [form.formState.isDirty]);
return null;
};
Dependencies
Requires React Hook Form context:
import { FormProvider, useForm } from 'react-hook-form';
const App = () => {
const form = useForm<CVData>();
return (
<FormProvider {...form}>
<VersionHistory />
</FormProvider>
);
};
Best Practices
Handle Loading States
if (isLoading) {
return <SkeletonList count={5} />;
}
if (error) {
return <ErrorMessage message={error} />;
}
return <VersionList versions={versions} />;
Confirm Destructive Actions
const handleDelete = async (versionId: string) => {
const confirmed = await confirm({
title: 'Delete Version?',
description: 'This action cannot be undone.',
confirmText: 'Delete'
});
if (confirmed) {
await deleteVersionById(versionId);
}
};
Reload After Restore
const handleRestore = async (versionId: string) => {
const success = await restoreToVersion(versionId);
if (success) {
toast.success('Version restored');
// Option 1: Reload page
window.location.reload();
// Option 2: Re-fetch and update form
const cv = await loadCV(userId);
form.reset(cv);
}
};