The server-confirmed product list (source of truth)
An object with results, addPendingResult, confirmResult,
and removeFailedResult functions.
const { results, addPendingResult, confirmResult, removeFailedResult } =
useOptimisticResultsWithPending(searchResults);
addPendingResult(product); // row appears with isPending: true
confirmResult(product); // isPending flips to false
removeFailedResult(product); // row is removed from the list
export function useOptimisticResultsWithPending(confirmedResults: Product[]) {
const [optimisticResults, addOptimisticResult] = useOptimistic(
confirmedResults,
(
state: Product[],
action: { type: "add" | "confirm" | "error"; product: Product; tempId?: string },
) => {
switch (action.type) {
case "add":
return [...state, { ...action.product, id: state.length, isPending: true }];
case "confirm":
return state.map((item) =>
item.id === action.product.id ? { ...action.product, isPending: false } : item,
);
case "error":
return state.filter((item) => item.id !== action.product.id);
default:
return state;
}
},
);
/**
* Add a product in the pending state (`isPending: true`).
* @param product - The product to insert optimistically
* @source
*/
const addPendingResult = (product: Product) => {
addOptimisticResult({ type: "add", product });
};
/**
* Mark a previously pending product as confirmed (`isPending: false`).
* @param product - The confirmed product (must have a matching `id`)
* @source
*/
const confirmResult = (product: Product) => {
addOptimisticResult({ type: "confirm", product });
};
/**
* Remove a product that failed processing from the optimistic list.
* @param product - The failed product (must have a matching `id`)
* @source
*/
const removeFailedResult = (product: Product) => {
addOptimisticResult({ type: "error", product });
};
return {
results: optimisticResults,
addPendingResult,
confirmResult,
removeFailedResult,
};
}
Extended optimistic-results hook that tracks a per-item pending state. Each product passes through three lifecycle stages:
add(pending) →confirm(persisted) orerror(removed). This lets the UI render a loading indicator on rows that are not yet confirmed.