Headless hook

useSitepingInbox — all the inbox logic, none of the UI. Build your own triage view.

Everything SitepingInbox does — fetching, filters, optimistic mutations, undo, pagination — lives in one hook you can drive from your own components:

import { useSitepingInbox } from "@siteping/dashboard";

const inbox = useSitepingInbox({
  projects: "my-project",
  endpoint: "/api/siteping",
});

Options

OptionTypeDefaultNotes
projectsstring or string[]requiredAn empty array throws at render. The first entry is selected initially
endpointstringHTTP mode
storeSitepingStoreStore mode — beats endpoint
sourceInboxSourceFully custom data source — beats both; see Data sources
apiKeystringAuthorization: Bearer on every request (endpoint mode)
headersobject or functionExtra headers; an explicit Authorization beats apiKey. Read live — changing it doesn't rebuild the source
pageSizenumber50Clamped to 1–100
onStatusChange(feedback, previousStatus) => voidAfter the server confirms
onDelete(feedback) => voidAfter the server confirms
onError(error) => voidEvery failed load or mutation

Providing none of source / store / endpoint throws immediately — there's no silent degraded mode.

What you get back

Dataitems (all loaded pages), total (null until the first page lands), counts (per-status totals, best-effort), loading, loadingMore, error, hasMore, loadMore(), refresh().

Filtersproject/setProject, status/setStatus (starts on "open"), type/setType, search/setSearch. Search updates state instantly and debounces the refetch by 250 ms; queries are trimmed and capped at 200 characters.

Focus & drawerfocusedId, focus(id), focusNext(), focusPrev(), openedId, opened (the open record — it stays available even if a filter removes its row), openFeedback(id), closeFeedback().

MutationschangeStatus(id, status), deleteFeedback(id), pendingUndo, undo().

Mutations are optimistic — and they re-throw

The UI updates immediately; if the server rejects, the hook rolls everything back (items, counts, focus, the open record) and then re-throws. Always attach a catch:

<button
  onClick={() => {
    inbox.changeStatus(item.id, "resolved").catch(() => {
      // state is already rolled back — surface a toast here
    });
  }}
>
  Resolve
</button>

Only the last status change is undoable (pendingUndo + undo()); delete is permanent, so gate it behind your own confirmation UI.

Pagination

loadMore() is manual — the bundled component renders a "Load more" button, not infinite scroll. Pages are deduplicated by id, and the next page number is derived from items.length, so optimistic removals never skip rows.

Edit on GitHub

On this page