newUpdatesTracker

newUpdatesTracker is a utility function for tracking updates to a list of data objects, enabling the execution of custom behaviors when items are entered, updated, or exited.

Implementation
export function newUpdatesTracker<D = unknown, R = D, K = D>({
  onEnter = ([data]) => data as unknown as R,
  onUpdate = ([data]) => data as unknown as R,
  onExit = () => void 0,
  getKey = (v: D) => v as unknown as K,
}: {
  onEnter?: (params: [data: D, index: number]) => R;
  onUpdate?: (
    params: [data: D, index: number],
    prevResult: R,
    prevParams: [data: D, index: number],
  ) => R;
  onExit?: (params: [data: D, index: number], result: R) => void;
  getKey?: (data: D) => K;
} = {}): (values: D[]) => R[] {
  let slotsIndex = new Map<K, [data: D, index: number] & { result: R }>();
  return (values: D[] = []) => {
    const newSlotsIndex = new Map<
      K,
      [data: D, index: number] & { result: R }
    >();
    const resultingValues: R[] = [];
    for (const data of values) {
      const key = getKey(data);
      const slot = slotsIndex.get(key);
      const idx = resultingValues.length;
      const nextSlot = [data, idx] as [data: D, index: number] & { result: R };
      nextSlot.result = slot
        ? onUpdate(nextSlot, slot.result, slot)
        : onEnter(nextSlot);
      slotsIndex.delete(key);
      newSlotsIndex.set(key, nextSlot);
      resultingValues.push(nextSlot.result);
    }
    for (const slotToExit of slotsIndex.values()) {
      onExit(slotToExit, slotToExit.result);
    }
    slotsIndex = newSlotsIndex;
    return resultingValues;
  };
}

This function is useful for managing dynamic lists where elements may change, be added, or be removed between updates:

Examples

const tracker = newUpdatesTracker({
  onEnter: ([data]) => `Entered: ${data}`,
  onUpdate: ([data], prevResult) => `${prevResult} -> Updated: ${data}`,
  onExit: ([data]) => console.log(`Exited: ${data}`),
});

const values1 = ['A', 'B'];
console.log(tracker(values1)); // ["Entered: A", "Entered: B"]

const values2 = ['B', 'C'];
console.log(tracker(values2)); // ["Entered: B -> Updated: B", "Entered: C"]

Advanced Example:

const tracker = newUpdatesTracker({
  onEnter: ([data, index]) => ({ value: data, index }),
  onUpdate: ([data, index], prevResult) => ({ ...prevResult, value: data }),
  onExit: ([data], result) => console.log(`Exited item:`, result),
  getKey: (data) => data.id,
});

const values1 = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
];
console.log(tracker(values1));
// [{ value: { id: 1, name: 'Alice' }, index: 0 }, { value: { id: 2, name: 'Bob' }, index: 1 }]

const values2 = [
  { id: 2, name: 'Bob (updated)' },
  { id: 3, name: 'Charlie' },
];
console.log(tracker(values2));
// [{ value: { id: 2, name: 'Bob (updated)' }, index: 0 }, { value: { id: 3, name: 'Charlie' }, index: 1 }]

Signature

function newUpdatesTracker<D = unknown, R = D, K = D>({
  onEnter,
  onUpdate,
  onExit,
  getKey,
}: {
  onEnter?: (params: [data: D, index: number]) => R;
  onUpdate?: (
    params: [data: D, index: number],
    prevResult: R,
    prevParams: [data: D, index: number]
  ) => R;
  onExit?: (params: [data: D, index: number], result: R) => void;
  getKey?: (data: D) => K;
}): (values: D[]) => R[];

Parameters:

Returns:

Behavior Details

  1. Tracking Lifecycle:
    • Enter: For new items, the onEnter function is invoked, generating an initial result.
    • Update: For existing items, the onUpdate function is called, allowing updates to the result based on new data and previous state.
    • Exit: For removed items, the onExit function is invoked with their last state.
  2. Key Matching:
    • Items are identified by keys extracted using getKey.
    • Keys must be unique within each update cycle.
  3. State Maintenance:
    • Internally tracks previous states and results using a Map.
    • Efficiently updates only the necessary items based on changes.

Notes

Additional Sections