# React-привязки

> CardProvider и хуки из @neurosquad/card-sdk/react — контекст, настройки, хранилище, агенты, порты, инструменты, тема, язык и видимость.

Source: https://docs.neurosquad.ai/ru/card-sdk/react

`@neurosquad/card-sdk/react` оборачивает карточку в провайдер и отдаёт её живое состояние через хуки.
React 18.2+ или 19 — peer-зависимость; шаблон React всё настраивает сам.

```tsx
import { createRoot } from 'react-dom/client'
import { CardProvider, useCardContext, useStorage } from '@neurosquad/card-sdk/react'

function App() {
  const { instance, workspace } = useCardContext()
  const [count, setCount] = useStorage('count', 0)
  return (
    <button className="ns-btn ns-btn--primary" onClick={() => setCount(count + 1)}>
      {instance.displayName} in {workspace.name}: {count}
    </button>
  )
}

createRoot(document.getElementById('root')!).render(
  <CardProvider fallback={<p>Connecting…</p>}>
    <App />
  </CardProvider>
)
```

## `CardProvider`

| Свойство | Значение |
| --- | --- |
| `card` | Карточка, которую вы подключили сами, — например, с [мок-хоста](https://docs.neurosquad.ai/ru/card-sdk/testing). Без неё провайдер сам вызывает `connect()`. |
| `connectOptions` | Параметры для `connect()`. |
| `fallback` | Показывается, пока идёт подключение. |
| `errorFallback` | `(error) => ReactNode`, показывается, если подключиться не удалось. По умолчанию — текст ошибки. |

Шаблон React сначала подключается, а потом передаёт `card` — так одна и та же точка входа работает и
в приложении, и, с мок-хостом, в превью в браузере.

## Хуки

| Хук | Возвращает |
| --- | --- |
| `useCard()` | [`Card`](https://docs.neurosquad.ai/ru/card-sdk/api#the-card-object) — для всего, у чего нет своего хука. |
| `useCardContext()` | Живой [`HostContext`](https://docs.neurosquad.ai/ru/card-sdk/api#context); перерисовывает при любом изменении. |
| `useSettings()` | `[settings, setSettings]` — `settings.values`, `settings.secrets`; сеттер меняет несекретные значения. |
| `useStorage(key, initial, { scope? })` | `[value, setValue, { loading, error }]` — как `useState`, только с сохранением. Сеттер обновляет сразу и пишет в фоне; принимает и функцию. С `scope: 'package'` следит за записями других копий карточки. |
| `useAgents()` | `{ agents, loading, error, refresh }` — обновляется по событиям статуса и изменений. Нужно `agents.read`. |
| `useAgentStatus(agentId)` | Статус одного агента или `undefined`. |
| `usePort(input?)` | `{ data, message }` — последнее значение, пришедшее на вход (или на любой вход). |
| `useEmit(output)` | Стабильная функция `(data) => Promise<number>`, отправляющая в выход. |
| `usePortRequest(input, handler)` | Отвечает на вход-запрос, пока компонент смонтирован. |
| `usePeers()` | Подключённые карточки и их порты, вживую. |
| `useTool(name, handler)` | Реализует инструмент из манифеста, пока компонент смонтирован; всегда вызывает последний обработчик. |
| `useCardEvent(event, handler)` | Любое событие, пока компонент смонтирован; всегда вызывает последний обработчик. |
| `useTheme()` | `ThemeSnapshot`, вживую (CSS-переменные уже применены). |
| `useLanguage()` | `{ language, locale }`, вживую. |
| `useTranslator(catalog)` | [Переводчик](https://docs.neurosquad.ai/ru/card-sdk/api/environment#i18n), перерисовывающий при смене языка. Каталог объявляйте вне компонента. |
| `useVisibility()` | `visible`, `offscreen`, `overview` или `hidden`. |
| `usePaused()` | `true`, когда тело карточки никто не видит, — ставьте на паузу анимации и опрос. |
| `useExpanded()` | Развёрнута ли карточка. |

## Пример побольше

```tsx
import type { Catalog } from '@neurosquad/card-sdk'
import {
  useAgents,
  useCard,
  useEmit,
  usePaused,
  usePort,
  useTool,
  useTranslator
} from '@neurosquad/card-sdk/react'
import { useEffect } from 'react'

const catalog: Catalog = {
  en: { waiting_one: '{{count}} agent waits for you', waiting_other: '{{count}} agents wait for you' },
  ru: {
    waiting_one: '{{count}} агент ждёт вас',
    waiting_few: '{{count}} агента ждут вас',
    waiting_many: '{{count}} агентов ждут вас',
    waiting_other: '{{count}} агента ждут вас'
  },
  zh: { waiting_other: '{{count}} 个智能体在等你' }
}

export function Waiting() {
  const card = useCard()
  const t = useTranslator(catalog)
  const paused = usePaused()
  const { agents } = useAgents()
  const waiting = agents.filter((a) => a.status === 'needs-input')
  const { data: note } = usePort<string>('notes')
  const emit = useEmit<string>('digest')

  // Keep the overview tile and attention in step with the data.
  useEffect(() => {
    void card.setOverview({ primary: t('waiting', { count: waiting.length }), icon: 'bell' })
    void card.attention(waiting.length > 0 ? 'needs-input' : 'none').catch(() => undefined)
  }, [card, t, waiting.length])

  useTool('list_waiting', () => waiting.map((a) => a.name))

  return (
    <div className="ns-stack">
      <p className={paused ? '' : 'pulse'}>{t('waiting', { count: waiting.length })}</p>
      {note ? <pre className="ns-mono">{note}</pre> : null}
      <button className="ns-btn" onClick={() => void emit(waiting.map((a) => `- ${a.name}`).join('\n'))}>
        Send digest
      </button>
    </div>
  )
}
```

> Хуки, которые обращаются к приложению (`useAgents`, `useStorage`), не бросают исключения, а
> сообщают об ошибках в поле `error` — нехватка разрешения появится там как `CardSdkError` с
> `PERMISSION_DENIED`.

## Локальная копия SDK

Если карточка подключает SDK по ссылке `file:`, npm создаёт символическую ссылку, и Vite может
загрузить вторую копию React рядом с SDK — тогда хуки падают с «Invalid hook call». В
`vite.config.ts` шаблона React уже есть `resolve: { dedupe: ['react', 'react-dom'] }`; в своей
сборке добавьте его сами или укажите `install-links=true` в `.npmrc` карточки.
