# Usage Guide

## 1. Wrap your app with LoaderProvider

```tsx
import React from "react";
import { LoaderProvider } from "react-dynamic-loader-states";
import { AppRoutes } from "./AppRoutes";

export default function Root() {
  return (
    <LoaderProvider>
      <AppRoutes />
    </LoaderProvider>
  );
}
```

## 2. Use page-level loaders

```tsx
import { useLoaderStates } from "react-dynamic-loader-states";

function DashboardPage() {
  const { isPageLoading, withPageLoader } = useLoaderStates();

  const loadPageData = async () => {
    await withPageLoader(fetch("/api/dashboard"), "dashboard-page");
  };

  return (
    <>
      <button onClick={loadPageData}>Load dashboard</button>
      {isPageLoading("dashboard-page") && <p>Loading dashboard...</p>}
    </>
  );
}
```

## 3. Use key-level loaders

```tsx
import axios from "axios";
import { useLoaderStates } from "react-dynamic-loader-states";

function SaveButton() {
  const { withKeyLoader, isKeyLoading } = useLoaderStates();

  const onSave = async () => {
    await withKeyLoader("save-button", () => axios.post("/api/save", { ok: true }));
  };

  return (
    <button onClick={onSave} disabled={isKeyLoading("save-button")}>
      {isKeyLoading("save-button") ? "Saving..." : "Save"}
    </button>
  );
}
```

## 4. Manual register/remove by key

Use this when wrapping non-promise workflows.

```tsx
import { useLoaderStates } from "react-dynamic-loader-states";

function ManualLoaderFlow() {
  const { registerLoader, removeLoader, isKeyLoading } = useLoaderStates();

  const runManualTask = async () => {
    registerLoader("manual-task");
    try {
      // custom async or event-driven task
      await new Promise((resolve) => setTimeout(resolve, 500));
    } finally {
      removeLoader("manual-task");
    }
  };

  return (
    <button onClick={runManualTask} disabled={isKeyLoading("manual-task")}>
      {isKeyLoading("manual-task") ? "Working..." : "Run"}
    </button>
  );
}
```

## 5. Promise wrappers with axios/fetch

The wrapper accepts either:

- A Promise instance
- A function that returns a Promise (recommended)

```tsx
const { withPageLoader, withKeyLoader } = useLoaderStates();

await withPageLoader(() => axios.get("/api/users"), "users-page");
await withKeyLoader("refresh-button", () => fetch("/api/refresh"));
```

## 6. Step-by-step runtime flow

### Provider initialization

1. App mounts inside LoaderProvider.
2. Internal store starts with empty counters:
   - pageCounts = {}
   - keyCounts = {}
3. Components reading useLoaderStates() subscribe to updates.

### Page-level loader flow

1. Call withPageLoader(promiseOrFactory, "dashboard-page").
2. startPageLoader("dashboard-page") runs.
3. pageCounts["dashboard-page"] increments.
4. isPageLoading("dashboard-page") returns true and UI can show a page/section loader.
5. Promise executes.
6. Promise resolves or rejects.
7. stopPageLoader("dashboard-page") runs in finally.
8. Counter decrements, and loader hides only when the count reaches zero.

### Key-level loader flow

1. Call withKeyLoader("save-button", promiseOrFactory).
2. registerLoader("save-button") runs.
3. keyCounts["save-button"] increments.
4. isKeyLoading("save-button") becomes true and UI can disable the button.
5. Promise executes.
6. Promise resolves or rejects.
7. removeLoader("save-button") runs in finally.
8. Counter decrements, and loader ends only when the count reaches zero.

### Manual register/remove flow

1. Call registerLoader("manual-task").
2. Run any custom operation.
3. Always call removeLoader("manual-task") inside finally.
4. If removeLoader is skipped, that loader key stays active.

### Parallel requests with same key

1. Two operations start on the same key.
2. Counter goes from 0 to 1 to 2.
3. First completion changes counter to 1 (still loading).
4. Second completion changes counter to 0 (loading ends).

### Promise input recommendation

- Prefer function input:
  - withKeyLoader("submit", () => apiCall())
- This guarantees loader starts before the request begins.
- Passing an already-created Promise can start work slightly before wrapper bookkeeping.

## Best Practices

- Prefer function form: withKeyLoader("k", () => apiCall()) so execution starts inside the wrapper.
- Use stable keys: submit-button, user-table-refresh, profile-page.
- Keep one semantic key per visual loader element.
- Always pair registerLoader/removeLoader in try/finally.
