> ## Documentation Index
> Fetch the complete documentation index at: https://docs.1up.vision/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks

> Store data, sync it in real-time, and access extension metadata with Vision SDK hooks.

Import hooks from `@1upvision/sdk`:

```tsx theme={null}
import {
  useExtensionStorage,
  useExtensionContext,
  useVisionState,
} from "@1upvision/sdk";
```

***

## useExtensionStorage

The main hook for reading and writing data. Storage is persisted on the server and syncs in real-time between all targets (editor, layer, interactive page).

```tsx theme={null}
const [storage, setStorage] = useExtensionStorage();
```

| Value        | Type                                      | Description                       |
| ------------ | ----------------------------------------- | --------------------------------- |
| `storage`    | `Record<string, unknown>`                 | The current stored data           |
| `setStorage` | `(data: Record<string, unknown>) => void` | Replace the entire storage object |

### Basic Usage

```tsx theme={null}
import {
  Vision,
  CompactView,
  Toggle,
  useExtensionStorage,
} from "@1upvision/sdk";

function Settings() {
  const [storage, setStorage] = useExtensionStorage();

  return (
    <CompactView title="Settings">
      <Toggle
        label="Enabled"
        checked={storage.enabled ?? false}
        onChange={(checked) => setStorage({ ...storage, enabled: checked })}
      />
    </CompactView>
  );
}

Vision.render(<Settings />, { target: "editor" });
```

### Real-Time Sync

When you call `setStorage` in any target, all other targets update automatically:

1. Streamer toggles a switch in the **editor panel**
2. The value is persisted to the server
3. The **OBS layer** receives the update and re-renders

No websocket setup, no polling, no extra code. It just works.

<Warning>
  `setStorage` replaces the **entire** storage object. Always spread the existing storage when updating a single key:

  ```tsx theme={null}
  // Correct — keeps existing data
  setStorage({ ...storage, myKey: newValue });

  // Wrong — erases everything except myKey
  setStorage({ myKey: newValue });
  ```
</Warning>

***

## useVisionState

`useVisionState` is a key-scoped state hook backed by extension storage.

Use it when you want `useState`-style ergonomics without manually spreading the
entire storage object on every update.

```tsx theme={null}
const [count, setCount] = useVisionState("count", 0);
```

| Value      | Type                                  | Description                            |
| ---------- | ------------------------------------- | -------------------------------------- |
| `value`    | `T`                                   | Current value for the given key        |
| `setValue` | `(next: T \| (prev: T) => T) => void` | Update only this key in shared storage |

### Example

```tsx theme={null}
import {
  Vision,
  CompactView,
  Text,
  Button,
  useVisionState,
} from "@1upvision/sdk";

function Counter() {
  const [count, setCount] = useVisionState("count", 0);

  return (
    <CompactView title="Counter">
      <Text content={`Count: ${count}`} />
      <Button label="Increment" onClick={() => setCount((prev) => prev + 1)} />
    </CompactView>
  );
}

Vision.render(<Counter />, { target: "editor" });
```

### When to use which

* `useExtensionStorage`: when you want to read/write the whole storage object
* `useVisionState`: when you want one key with `useState`-style updates

***

## useExtensionContext

Returns metadata about the running extension instance. Useful for rendering different UIs per target or for debugging.

```tsx theme={null}
const context = useExtensionContext();
```

Returns `null` until the extension is initialized, then:

| Property         | Type                                   | Description                                       |
| ---------------- | -------------------------------------- | ------------------------------------------------- |
| `extensionId`    | `string`                               | Unique ID of this extension                       |
| `target`         | `"editor" \| "layer" \| "interactive"` | Which target is running                           |
| `overlayId`      | `string`                               | The overlay this extension belongs to             |
| `interactiveUrl` | `string?`                              | Absolute URL to this extension's interactive page |

### Build an Interactive Link

```tsx theme={null}
const context = useExtensionContext();

let joinUrl = "";
if (context?.interactiveUrl) {
  const url = new URL(context.interactiveUrl);
  url.searchParams.set("player", "1");
  joinUrl = url.toString();
}
```

### Render Different UI per Target

```tsx theme={null}
import {
  Vision,
  CompactView,
  Text,
  Button,
  useExtensionContext,
  useExtensionStorage,
} from "@1upvision/sdk";

function MyExtension() {
  const context = useExtensionContext();
  const [storage, setStorage] = useExtensionStorage();

  if (!context) return null;

  if (context.target === "editor") {
    return (
      <CompactView title="Settings">
        <Text content="Configure your extension here." variant="muted" />
      </CompactView>
    );
  }

  if (context.target === "interactive") {
    return (
      <CompactView title="Controls">
        <Button
          label="Trigger Effect"
          onClick={() => setStorage({ ...storage, trigger: Date.now() })}
        />
      </CompactView>
    );
  }

  // Layer target — display only
  return <Text content={storage.message ?? "Hello!"} variant="heading" />;
}

Vision.render(<MyExtension />);
```

<Tip>
  If your targets share very little UI, it's usually cleaner to use separate
  entry point files (`admin.tsx`, `layer.tsx`, `interactive.tsx`) instead of
  branching with `useExtensionContext`.
</Tip>

***

## useQuery and useMutation

If your extension uses [server functions](/extensions-sdk/server-functions), two additional hooks are available for calling them from the client:

```tsx theme={null}
import { useQuery, useMutation } from "@1upvision/sdk";
```

### useQuery

Calls a server query function by name. Returns the data, loading state, and error.

```tsx theme={null}
const { data, isLoading, error, refetch } = useQuery("getPlayers");
const { data: player } = useQuery("getPlayerById", { id: "abc123" });
```

| Value       | Type             | Description                      |
| ----------- | ---------------- | -------------------------------- |
| `data`      | `T \| undefined` | The query result                 |
| `isLoading` | `boolean`        | Whether the query is in progress |
| `error`     | `Error \| null`  | Error if the query failed        |
| `refetch`   | `() => void`     | Manually re-run the query        |

### useMutation

Calls a server mutation or action function by name. Automatically invalidates active queries on success.

```tsx theme={null}
const { mutate, mutateAsync, isLoading, error } = useMutation("addPlayer");

// Fire-and-forget
mutate({ name: "Alice", score: 100 });

// Async with error handling
const result = await mutateAsync({ name: "Bob", score: 200 });
```

| Value         | Type                                             | Description                         |
| ------------- | ------------------------------------------------ | ----------------------------------- |
| `mutate`      | `(args?: Record<string, unknown>) => void`       | Fire-and-forget invocation          |
| `mutateAsync` | `(args?: Record<string, unknown>) => Promise<T>` | Async invocation with return value  |
| `data`        | `T \| undefined`                                 | The last mutation result            |
| `isLoading`   | `boolean`                                        | Whether the mutation is in progress |
| `error`       | `Error \| null`                                  | Error if the mutation failed        |

### Which hook for which function type

| Server function                                          | Call with     |
| -------------------------------------------------------- | ------------- |
| `query(...)` / `queryRows(...)`                          | `useQuery`    |
| `mutation(...)` / `insertRow` / `patchRow` / `deleteRow` | `useMutation` |
| `action(...)` / `fetchAction(...)`                       | `useMutation` |

<Warning>
  Using the wrong hook causes a `FUNCTION_NOT_FOUND` error at runtime. For
  example, calling a `mutation` with `useQuery` will fail.
</Warning>

See [Server Functions](/extensions-sdk/server-functions) for full details on defining functions.
