mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 12:31:26 -05:00
Refactor: Implement non-disruptive auto-refresh
This commit is contained in:
159
src/lib/smartStateUpdate.ts
Normal file
159
src/lib/smartStateUpdate.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Smart State Update Utility
|
||||
*
|
||||
* Provides intelligent array diffing and merging to prevent
|
||||
* unnecessary re-renders and preserve user interaction state.
|
||||
*/
|
||||
|
||||
export interface SmartMergeOptions<T> {
|
||||
compareFields?: (keyof T)[];
|
||||
preserveOrder?: boolean;
|
||||
addToTop?: boolean;
|
||||
}
|
||||
|
||||
export interface MergeChanges<T> {
|
||||
added: T[];
|
||||
removed: T[];
|
||||
updated: T[];
|
||||
}
|
||||
|
||||
export interface SmartMergeResult<T> {
|
||||
items: T[];
|
||||
changes: MergeChanges<T>;
|
||||
hasChanges: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs intelligent array diffing and merging
|
||||
*
|
||||
* @param currentItems - The current items in state
|
||||
* @param newItems - The new items fetched from the server
|
||||
* @param options - Configuration options
|
||||
* @returns Merged items with change information
|
||||
*/
|
||||
export function smartMergeArray<T extends { id: string }>(
|
||||
currentItems: T[],
|
||||
newItems: T[],
|
||||
options?: SmartMergeOptions<T>
|
||||
): SmartMergeResult<T> {
|
||||
const {
|
||||
compareFields,
|
||||
preserveOrder = false,
|
||||
addToTop = true,
|
||||
} = options || {};
|
||||
|
||||
// Create ID maps for quick lookup
|
||||
const currentMap = new Map(currentItems.map(item => [item.id, item]));
|
||||
const newMap = new Map(newItems.map(item => [item.id, item]));
|
||||
|
||||
// Detect changes
|
||||
const added: T[] = [];
|
||||
const removed: T[] = [];
|
||||
const updated: T[] = [];
|
||||
|
||||
// Find added and updated items
|
||||
for (const newItem of newItems) {
|
||||
const currentItem = currentMap.get(newItem.id);
|
||||
|
||||
if (!currentItem) {
|
||||
// New item
|
||||
added.push(newItem);
|
||||
} else if (hasItemChanged(currentItem, newItem, compareFields)) {
|
||||
// Item has changed
|
||||
updated.push(newItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Find removed items
|
||||
for (const currentItem of currentItems) {
|
||||
if (!newMap.has(currentItem.id)) {
|
||||
removed.push(currentItem);
|
||||
}
|
||||
}
|
||||
|
||||
const hasChanges = added.length > 0 || removed.length > 0 || updated.length > 0;
|
||||
|
||||
// If no changes, return current items (preserves object references)
|
||||
if (!hasChanges) {
|
||||
return {
|
||||
items: currentItems,
|
||||
changes: { added: [], removed: [], updated: [] },
|
||||
hasChanges: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Build merged array
|
||||
let mergedItems: T[];
|
||||
|
||||
if (preserveOrder) {
|
||||
// Preserve the order of current items
|
||||
mergedItems = currentItems
|
||||
.filter(item => !removed.some(r => r.id === item.id))
|
||||
.map(item => {
|
||||
const updatedItem = updated.find(u => u.id === item.id);
|
||||
return updatedItem || item; // Use updated version if available, otherwise keep current
|
||||
});
|
||||
|
||||
// Add new items at top or bottom
|
||||
if (addToTop) {
|
||||
mergedItems = [...added, ...mergedItems];
|
||||
} else {
|
||||
mergedItems = [...mergedItems, ...added];
|
||||
}
|
||||
} else {
|
||||
// Use the order from newItems
|
||||
mergedItems = newItems.map(newItem => {
|
||||
const currentItem = currentMap.get(newItem.id);
|
||||
|
||||
// If item exists in current state and hasn't changed, preserve reference
|
||||
if (currentItem && !updated.some(u => u.id === newItem.id)) {
|
||||
return currentItem;
|
||||
}
|
||||
|
||||
return newItem;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
items: mergedItems,
|
||||
changes: { added, removed, updated },
|
||||
hasChanges: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an item has changed by comparing specific fields
|
||||
*/
|
||||
function hasItemChanged<T>(
|
||||
currentItem: T,
|
||||
newItem: T,
|
||||
compareFields?: (keyof T)[]
|
||||
): boolean {
|
||||
if (!compareFields || compareFields.length === 0) {
|
||||
// Deep comparison if no specific fields provided
|
||||
return JSON.stringify(currentItem) !== JSON.stringify(newItem);
|
||||
}
|
||||
|
||||
// Compare only specified fields
|
||||
for (const field of compareFields) {
|
||||
if (currentItem[field] !== newItem[field]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a stable ID set for tracking interactions
|
||||
*/
|
||||
export function createStableIdSet(ids: string[]): Set<string> {
|
||||
return new Set(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an ID is in an interaction set
|
||||
*/
|
||||
export function isInteractingWith(id: string, interactionSet: Set<string>): boolean {
|
||||
return interactionSet.has(id);
|
||||
}
|
||||
Reference in New Issue
Block a user