# How to Implement Bulk Delete with Table Selection and bulkDeleteAction in refine-shadcn

> Learn to implement bulk delete in refine-shadcn using table selection and bulkDeleteAction. This guide shows how to harness the power of TanStack Table and refine hooks for efficient data management.

- Repository: [Ferdi ÜNAL/refine-shadcn](https://github.com/ferdiunal/refine-shadcn)
- Tags: how-to-guide
- Published: 2026-03-01

---

**To implement bulk delete in refine‑shadcn, create a factory function that receives the TanStack Table instance, extracts selected rows via `table.getSelectedRowModel().rows`, invokes the `useDeleteHelper` hook for each record, and passes the resulting `{ label, onClick }` object to the `options` prop of `Table.CheckAll` located in [`packages/theme/src/table/fields/checkall.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/table/fields/checkall.tsx).**

The refine‑shadcn UI toolkit extends Refine with shadcn/ui components and provides a **Table** primitive that already handles row selection through the `CheckAll` component. Because the library exposes the full TanStack Table API alongside custom hooks like `useDeleteHelper`, implementing bulk deletion requires no changes to core components—only composition of existing utilities in your list page.

## How Table Selection Works in refine‑shadcn

The selection UI is managed by **CheckAll** in [`packages/theme/src/table/fields/checkall.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/table/fields/checkall.tsx). This component renders the master checkbox in the table header and exposes a dropdown menu that accepts an `options` array. Each option expects a `label` string and an `onClick` handler, allowing you to inject custom bulk actions such as “Delete Selected”.  

Row‑level deletions are handled by **DeleteAction** in [`packages/theme/src/table/actions/delete.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/table/actions/delete.tsx), which consumes **DeleteContext** from [`packages/theme/src/providers/deleteProvider.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/providers/deleteProvider.tsx). The actual network logic lives in [`packages/theme/src/hooks/useDeleteHelper.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/hooks/useDeleteHelper.tsx), a wrapper around Refine’s `useDelete` that enforces access control and mutation modes. Bulk deletion follows the same pattern: iterate over selected rows and call the helper for each ID.

## Creating the bulkDeleteAction Factory

In your list page (for example, [`templates/vite-react/src/pages/users/list.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/templates/vite-react/src/pages/users/list.tsx)), define a factory that receives the table instance and returns an action object:

```typescript
import { useContext } from "react";
import { DeleteContext, useDeleteHelper } from "@ferdiunal/refine-shadcn";
import { useUserFriendlyName } from "@refinedev/core";
import { UseTableReturnType, BaseRecord, HttpError } from "@refinedev/react-table";

const BulkDeleteAction = (table: UseTableReturnType<BaseRecord, HttpError>) => {
    const friendly = useUserFriendlyName();
    const selectedRows = table.getSelectedRowModel().rows;
    
    const label = `Delete Selected (${selectedRows.length}) ${friendly(
        "Row",
        selectedRows.length > 1 ? "plural" : "singular",
    )}`;

    const { mutate, isLoading } = useDeleteHelper("", "");

    const onClick = async () => {
        if (!selectedRows.length) return;
        
        if (!window.confirm(`Delete ${selectedRows.length} selected rows?`)) return;

        await Promise.all(
            selectedRows.map(row => 
                mutate({
                    resource: "users", // Replace with your resource name
                    id: row.original.id,
                })
            ),
        );

        table.toggleAllPageRowsSelected(false);
    };

    return { label, onClick, disabled: selectedRows.length === 0 || isLoading };
};

```

**Key implementation details:**

- **`table.getSelectedRowModel().rows`** provides the array of selected row objects.
- **`useDeleteHelper`** accepts empty strings for resource and ID because you will supply them per‑call inside the loop.
- **`Promise.all`** executes deletions concurrently; alternatively, use `for…of` for sequential processing.
- **`table.toggleAllPageRowsSelected(false)`** clears the selection state after the operation succeeds.

## Wiring the Action to Table.CheckAll

Pass the factory to the `options` prop of `Table.CheckAll` inside your table definition:

```tsx
import { ListPage, Table } from "@ferdiunal/refine-shadcn";
import { Checkbox } from "@/components/ui/checkbox";
import { BulkDeleteAction } from "./BulkDeleteAction";

export const UserList = () => {
    return (
        <ListPage>
            <Table enableSorting enableFilters>
                <Table.Column
                    accessorKey="id"
                    id="select"
                    header={({ table }) => (
                        <Table.CheckAll
                            options={[BulkDeleteAction(table)]}
                            table={table}
                        />
                    )}
                    cell={({ row }) => (
                        <Checkbox
                            checked={row.getIsSelected()}
                            onCheckedChange={value => row.toggleSelected(!!value)}
                        />
                    )}
                />
                <Table.Column accessorKey="firstName" header="First Name" />
                <Table.Column
                    accessorKey="id"
                    id="actions"
                    cell={({ row: { original } }) => (
                        <Table.Actions>
                            <Table.ShowAction resource="users" row={original} />
                            <Table.EditAction resource="users" row={original} />
                            <Table.DeleteAction resource="users" row={original} />
                        </Table.Actions>
                    )}
                />
            </Table>
        </ListPage>
    );
};

```

The `options` array accepts multiple bulk actions, so you can extend `[BulkDeleteAction(table)]` with additional handlers for export or status updates.

## (Optional) Reusing the Delete Confirmation Modal for Bulk

To leverage the existing confirmation modal from `DeleteProvider` instead of `window.confirm`, extend the context type in [`packages/theme/src/providers/deleteProvider.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/providers/deleteProvider.tsx):

```typescript
type DeleteDataType = {
    toogle: boolean;
    rows?: any[]; // Add this for bulk support
    resource: string;
    redirectBack?: boolean;
    onAfterHandle?: () => void;
};

```

Then update your bulk action to trigger the modal:

```typescript
const deleteContext = useContext(DeleteContext);

const onClick = () => {
    deleteContext?.updateData({
        rows: selectedRows.map(r => r.original),
        resource: "users",
        toogle: true,
        onAfterHandle: () => table.toggleAllPageRowsSelected(false),
    });
};

```

You would then modify the modal content to render the row count and, on confirmation, iterate over `rows` to call `mutate` for each item, keeping the UI consistent with single‑row deletions.

## Summary

- **CheckAll** in [`packages/theme/src/table/fields/checkall.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/table/fields/checkall.tsx) accepts bulk actions through its `options` prop.
- Access selected rows via `table.getSelectedRowModel().rows` provided by `@refinedev/react-table`.
- Execute deletions with **`useDeleteHelper`** from [`packages/theme/src/hooks/useDeleteHelper.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/hooks/useDeleteHelper.tsx) to preserve access control and mutation modes.
- Clear selection after deletion using **`table.toggleAllPageRowsSelected(false)`**.
- No backend changes are required because `useDeleteHelper` maps to the standard Refine `DELETE /{resource}/{id}` endpoint.

## Frequently Asked Questions

### How does Table.CheckAll receive custom bulk actions?

`Table.CheckAll` accepts an **`options`** prop that expects an array of objects shaped as `{ label: string, onClick: () => void, disabled?: boolean }`. Each object renders as a dropdown item next to the master checkbox, allowing you to wire arbitrary bulk operations such as delete, export, or status change.

### Can I delete selected rows sequentially instead of concurrently?

Yes. Replace `Promise.all` with a `for…of` loop inside the `onClick` handler. Sequential execution is useful when your API has strict rate limits or when deletions have side‑effects that require ordering, though it will take longer for large selections.

### Where is the delete confirmation modal defined in refine-shadcn?

The confirmation modal is defined in **[`packages/theme/src/providers/deleteProvider.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/providers/deleteProvider.tsx)**. It exposes `DeleteContext`, which is consumed by `Table.DeleteAction` and can also be imported into custom bulk actions if you want to reuse the same UI for confirming multi‑row deletions.

### Why does my bulk action lose the table selection after navigation?

TanStack Table stores selection state in the component instance. If you navigate away and return, the table remounts and selection resets. To persist selection across sessions, pass a `state.rowSelection` controlled prop to the table and synchronize it with URL search params or local storage.