Skip to main content

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

useItemHistory

Fetches paginated audit history for a specific item from the admin API. Each log entry records an action (create, update, status change, etc.), who performed it, and what changed. Supports filtering by action type and uses keepPreviousData for smooth pagination transitions.

Source: template/hooks/use-item-history.ts

Exported Hooks

HookPurpose
useItemHistoryFetch paginated audit log entries for a single item

Exported Types

export interface ItemAuditLogEntry {
id: string;
itemId: string;
itemName: string;
action: ItemAuditActionValues;
previousStatus: string | null;
newStatus: string | null;
changes: Record<string, { old: unknown; new: unknown }> | null;
performedBy: string | null;
performedByName: string | null;
notes: string | null;
metadata: Record<string, unknown> | null;
createdAt: string;
performer: {
id: string;
email: string | null;
} | null;
}

export interface ItemHistoryResponse {
success: boolean;
data: {
logs: ItemAuditLogEntry[];
total: number;
page: number;
limit: number;
totalPages: number;
};
error?: string;
}

export interface UseItemHistoryParams {
itemId: string;
page?: number;
limit?: number;
actionFilter?: ItemAuditActionValues[];
enabled?: boolean;
}

Exported Constants

export const ITEM_HISTORY_QUERY_KEYS = {
all: ['admin', 'items', 'history'] as const,
item: (itemId: string) =>
[...ITEM_HISTORY_QUERY_KEYS.all, itemId] as const,
itemWithParams: (itemId: string, params: { page?: number; actionFilter?: ItemAuditActionValues[] }) =>
[...ITEM_HISTORY_QUERY_KEYS.item(itemId), params] as const,
} as const;

Signature

function useItemHistory(params: UseItemHistoryParams): UseQueryResult<ItemHistoryResponse['data']>;

Parameters

FieldTypeDefaultDescription
itemIdstringRequired. The item ID whose history to fetch.
pagenumber1Page number (1-based)
limitnumber20Number of log entries per page
actionFilterItemAuditActionValues[]Optional array of action types to filter by
enabledbooleantrueDisable the query when false

Return Values

This hook returns a standard @tanstack/react-query UseQueryResult object. The data field contains:

{
logs: ItemAuditLogEntry[]; // Array of audit log entries
total: number; // Total number of matching entries
page: number; // Current page
limit: number; // Entries per page
totalPages: number; // Total number of pages
}

Common fields from the query result:

const {
data, // ItemHistoryResponse['data'] | undefined
isLoading, // boolean -- True on initial fetch
isFetching, // boolean -- True during any fetch
isError, // boolean -- True if fetch failed
error, // Error | null
isPlaceholderData, // boolean -- True when showing previous page data
refetch, // () => void -- Re-execute the query
} = useItemHistory(params);

Cache Configuration

SettingValue
Query key['admin', 'items', 'history', itemId, { page, actionFilter }]
staleTime30 seconds
placeholderDatakeepPreviousData (shows old page while fetching new)
enabledparams.enabled && !!params.itemId

Implementation Details

  • Admin API: Fetches from /api/admin/items/:itemId/history, encoding the item ID for URL safety.
  • Action filtering: When actionFilter is provided and non-empty, the filter values are joined with commas and sent as the action query parameter.
  • keepPreviousData: The placeholderData: keepPreviousData option ensures that when paginating, the previous page's data remains visible until the new page loads, preventing layout flash.
  • Short stale time: Uses 30-second staleTime (vs. the typical 5 minutes for item data) because audit logs can change frequently as admins take actions.
  • Changes tracking: Each ItemAuditLogEntry includes a changes field that maps field names to { old, new } pairs, enabling detailed diff display.

Usage: Basic Audit History

function ItemHistory({ itemId }: { itemId: string }) {
const { data, isLoading } = useItemHistory({ itemId });

if (isLoading) return <Skeleton />;

return (
<div>
<h3>Audit History ({data?.total} entries)</h3>
{data?.logs.map((entry) => (
<div key={entry.id} className="border-b py-2">
<span className="font-medium">{entry.action}</span>
<span className="text-muted-foreground ml-2">
by {entry.performedByName || 'System'}
</span>
<span className="text-sm ml-2">
{new Date(entry.createdAt).toLocaleString()}
</span>
{entry.previousStatus && entry.newStatus && (
<span className="ml-2">
{entry.previousStatus} &rarr; {entry.newStatus}
</span>
)}
</div>
))}
</div>
);
}

Usage: Paginated with Action Filter

function FilteredHistory({ itemId }: { itemId: string }) {
const [page, setPage] = useState(1);
const [filter, setFilter] = useState<ItemAuditActionValues[]>([]);

const { data, isLoading, isPlaceholderData } = useItemHistory({
itemId,
page,
limit: 10,
actionFilter: filter.length > 0 ? filter : undefined,
});

return (
<div>
<select
multiple
onChange={(e) => {
const values = Array.from(e.target.selectedOptions, (o) => o.value);
setFilter(values as ItemAuditActionValues[]);
setPage(1);
}}
>
<option value="created">Created</option>
<option value="updated">Updated</option>
<option value="status_changed">Status Changed</option>
<option value="deleted">Deleted</option>
</select>

<div style={{ opacity: isPlaceholderData ? 0.5 : 1 }}>
{data?.logs.map((entry) => (
<HistoryEntry key={entry.id} entry={entry} />
))}
</div>

<div className="flex gap-2 mt-4">
<button onClick={() => setPage((p) => Math.max(1, p - 1))} disabled={page <= 1}>
Previous
</button>
<span>Page {page} of {data?.totalPages || 1}</span>
<button
onClick={() => setPage((p) => p + 1)}
disabled={page >= (data?.totalPages || 1)}
>
Next
</button>
</div>
</div>
);
}

Usage: Display Field Changes

function ChangesDiff({ changes }: { changes: Record<string, { old: unknown; new: unknown }> | null }) {
if (!changes) return null;

return (
<div className="bg-muted p-2 rounded text-sm">
{Object.entries(changes).map(([field, { old: oldVal, new: newVal }]) => (
<div key={field}>
<span className="font-medium">{field}:</span>{' '}
<span className="line-through text-red-500">{String(oldVal)}</span>{' '}
<span className="text-green-500">{String(newVal)}</span>
</div>
))}
</div>
);
}

Dependencies

DependencyPurpose
@tanstack/react-queryuseQuery, keepPreviousData for paginated caching
@/lib/api/server-api-clientserverClient for API calls, apiUtils for response validation
@/lib/db/schemaItemAuditActionValues type for action enum