# How to Use the Loading Component and Manage Loading States in refine-shadcn

> Learn how to use refine-shadcn Loading components to manage loading states effectively. Wire LoadingIcon or Loader to refine hooks like useTable for a better user experience.

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

---

**Use the `LoadingIcon` or `Loader` components from the theme package and wire them to the Boolean loading flags (e.g., `isLoading`, `formLoading`) exposed by refine hooks like `useTable`, `useOne`, and `useForm`.**

The `ferdiunal/refine-shadcn` repository provides lightweight, decoupled loading primitives designed to integrate seamlessly with refine data hooks. These components allow you to manage loading states in refine-shadcn applications without coupling your UI logic to the underlying data-fetching implementation.

## Loading Primitives Available in refine-shadcn

The theme package exposes two distinct loading components that serve different visual purposes. Both accept a `className` prop for Tailwind CSS styling.

### LoadingIcon for Inline Spinners

Located in [`packages/theme/src/ui/loading.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/ui/loading.tsx), the `LoadingIcon` component renders a spinning Lucide `RefreshCwIcon` as an inline SVG. This primitive is ideal for button states, inline indicators, or compact UI elements requiring immediate feedback. Pass Tailwind classes like `h-4 w-4 animate-spin` to control dimensions and animation speed.

### Loader for Block-Level Placeholders

Found in [`packages/theme/src/components/loader.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/components/loader.tsx), the `Loader` component displays a three-dot animated SVG suitable for larger placeholders. Use this inside table bodies, empty states, or full-screen overlays where a prominent loading indicator improves perceived performance.

## Wiring Loading States to Data Hooks

In refine-shadcn, loading UI is driven by Boolean flags exposed through refine core hooks. These flags turn `true` when a query initiates and revert to `false` upon completion, regardless of success or error states.

### Table Data Loading

When using `useTable`, access the loading state via `table.refineCore.tableQuery.isLoading`. In [`packages/theme/src/table/index.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/table/index.tsx), the implementation checks this flag to render a full-width table row containing the `Loader` component while data fetches.

### Form Submission States

Form components receive loading status through `props.refineCore.formLoading`. As shown in [`packages/theme/src/components/form.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/components/form.tsx), this flag disables the cancel button and triggers the spinner inside the `SaveButton` component automatically when passed the `loading` prop.

### Related Record Fetching

For fetching associated data, `useOne` exposes an `isLoading` flag. The example in [`templates/vite-react/src/pages/posts/Show.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/templates/vite-react/src/pages/posts/Show.tsx) demonstrates conditionally rendering "Loading…" text while fetching category details for a post.

## Implementation Examples

### Inline Button Loading State

The following pattern from [`packages/theme/src/components/confirm.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/components/confirm.tsx) shows how to swap an action icon for a `LoadingIcon` during async operations:

```tsx
import { LoadingIcon } from "@/ui/loading";
import { CheckIcon } from "lucide-react";
import {
  AlertDialog,
  AlertDialogAction,
} from "@/components/alert-dialog";

export const ConfirmDialog = ({
  loading = false,
  onConfirm,
}) => {
  const OkIcon = loading ? (
    <LoadingIcon className="mr-2" />
  ) : (
    <CheckIcon className="mr-2 h-4 w-4" />
  );

  return (
    <AlertDialogAction onClick={onConfirm} disabled={loading}>
      {OkIcon}
      Confirm
    </AlertDialogAction>
  );
};

```

### Table Body Loading Placeholder

This implementation from [`packages/theme/src/table/index.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/table/index.tsx) demonstrates handling empty table states:

```tsx
import Loader from "@/components/loader";
import { useTable } from "@refinedev/react-table";
import {
  TableBody,
  TableRow,
  TableCell,
} from "@/components/table";

export const Table = (props) => {
  const { table } = useTable({ ...props });

  return (
    <TableBody>
      {table.refineCore.tableQuery.isLoading ? (
        <TableRow>
          <TableCell colSpan={columns.length} className="h-24 text-center">
            <Loader className="h-4 text-primary" />
          </TableCell>
        </TableRow>
      ) : (
        table.getRowModel().rows.map((row) => (
          // render row
        ))
      )}
    </TableBody>
  );
};

```

### Form Submit Button Spinner

As implemented in [`packages/theme/src/components/form.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/components/form.tsx), wire the `formLoading` flag to your submit button:

```tsx
import { SaveButton } from "@/ui/buttons";
import { Button } from "@/components/button";
import { CardFooter } from "@/components/card";
import { Form as FormUI } from "@/components/form";

export const Form = (props) => {
  return (
    <FormUI {...props}>
      {/* form fields */}
      <CardFooter className="flex justify-end gap-x-4">
        <Button
          type="button"
          onClick={onBack}
          disabled={props.refineCore.formLoading}
          variant="outline"
        >
          Cancel
        </Button>
        <SaveButton
          type="submit"
          loading={props.refineCore.formLoading}
        />
      </CardFooter>
    </FormUI>
  );
};

```

### Dependent Data Fetching

From [`templates/vite-react/src/pages/posts/Show.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/templates/vite-react/src/pages/posts/Show.tsx), handle dependent data loading with `useOne`:

```tsx
import { useOne, useShow } from "@refinedev/core";

export const PostShow = () => {
  const { query: { data } } = useShow<IPost>();
  const record = data?.data;

  const { data: categoryData, isLoading: categoryIsLoading } = useOne<ICategory>({
    resource: "categories",
    id: record?.category.id ?? "",
    queryOptions: { enabled: !!record },
  });

  return (
    <ShowPage>
      <ShowPage.Row
        title="Category"
        children={categoryIsLoading ? "Loading…" : categoryData?.data.title ?? ""}
      />
    </ShowPage>
  );
};

```

### Global Suspense Boundaries

For application-wide loading states in Next.js, wrap your routes with React `Suspense` as shown in [`templates/nextjs/app/layout.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/templates/nextjs/app/layout.tsx):

```tsx
import { Suspense } from "react";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Suspense fallback={<div>Loading…</div>}>
          {children}
        </Suspense>
      </body>
    </html>
  );
}

```

## Summary

- **Import primitives** from [`packages/theme/src/ui/loading.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/ui/loading.tsx) (`LoadingIcon`) or [`packages/theme/src/components/loader.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/components/loader.tsx) (`Loader`) depending on your visual needs.
- **Consume loading flags** from refine hooks: `useTable` provides `table.refineCore.tableQuery.isLoading`, `useOne` provides `isLoading`, and forms receive `props.refineCore.formLoading`.
- **Apply Tailwind classes** via the `className` prop to size and color your spinners consistently with your design system.
- **Maintain decoupled architecture** by keeping loading UI in the theme package and data logic in your hooks, enabling easy spinner swaps without touching data-fetching code.

## Frequently Asked Questions

### How do I change the spinner color in refine-shadcn loading components?

Both `LoadingIcon` and `Loader` accept a `className` prop. Pass standard Tailwind color utilities like `text-primary` or `text-blue-500` to control the SVG stroke color. For example: `<Loader className="h-4 text-primary" />` applies the primary color to the three-dot animation.

### Can I use these loading components outside of refine data hooks?

Yes. While designed to integrate with refine's `isLoading` flags, these are pure presentational components. You can use them with any Boolean state variable, React Query's `isPending`, or manual `useState` toggles for custom async operations.

### Where should I place a full-page loading fallback in a Next.js app with refine-shadcn?

Wrap your application in a React `Suspense` boundary within your root layout, as shown in [`templates/nextjs/app/layout.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/templates/nextjs/app/layout.tsx). Provide a static fallback like `<div>Loading…</div>` or the `Loader` component for server-side rendered routes and lazy-loaded chunks.

### Why does my SaveButton not show a spinner even when I set loading={true}?

Ensure you are importing the `SaveButton` from the theme package (`@/ui/buttons`) as implemented in [`packages/theme/src/components/form.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/components/form.tsx). The button must internally support the `loading` prop and render a `LoadingIcon` when true. If using a standard shadcn button, you will need to manually compose it with `LoadingIcon` following the pattern in [`packages/theme/src/components/confirm.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/components/confirm.tsx).