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