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

# React Integration

> The useSystem hook for typed state and actions

# React Integration

The `useSystem` hook connects your React components to an Idyllic system. It establishes a WebSocket connection, subscribes to state updates, and returns typed state and actions. When server-side state changes, your component re-renders automatically.

```typescript theme={null}
import { useSystem } from '@idyllic/react';
import type { DocImprover } from '../systems/DocImprover';

function Editor() {
  const { content, improved, status, setContent, improve } = useSystem<DocImprover>();

  return (
    <div>
      <textarea value={content} onChange={e => setContent(e.target.value)} />
      <button onClick={improve}>Improve</button>
      <div>{improved.current}</div>
    </div>
  );
}
```

The generic parameter `<DocImprover>` tells TypeScript which system you're connecting to. This enables the framework to infer types for state and actions from your system definition—full autocompletion and type checking without client-side type definitions.

## What useSystem Returns

The hook returns an object containing your system's state properties and action methods, plus connection status:

```typescript theme={null}
const { count, tasks, generate, status, error } = useSystem<MySystem>();
```

State properties (`count`, `tasks`) are reactive—reading them causes re-renders when they change. Action methods (`generate`) forward calls to the server over WebSocket. The `status` property indicates connection state, and `error` contains failure details if applicable.

| Property     | Description                                                |
| ------------ | ---------------------------------------------------------- |
| State fields | Reactive values that trigger re-renders on change          |
| Actions      | Typed methods that execute on the server                   |
| `status`     | `'connecting' \| 'connected' \| 'disconnected' \| 'error'` |
| `error`      | Error object if connection failed                          |

## Reading State

State values read like regular JavaScript properties. The framework tracks which properties your component accesses and re-renders when they change:

```tsx theme={null}
const { count, tasks, user } = useSystem<MySystem>();

<div>{count}</div>
<ul>
  {tasks.map(task => <li key={task.id}>{task.title}</li>)}
</ul>
<span>{user.name}</span>
```

### Stream Values

For streaming content like LLM output, use the `current` property to display accumulated content and `status` to show indicators:

```tsx theme={null}
const { response } = useSystem<Writer>();

<div>
  {response.current}
  {response.status === 'streaming' && <span className="animate-pulse">|</span>}
</div>
```

Each chunk appended on the server triggers a re-render, creating the characteristic effect of tokens appearing progressively.

## Calling Actions

Actions are typed proxies that forward calls to the server. Call them like regular async functions:

```typescript theme={null}
const { increment, createTask, analyze } = useSystem<MySystem>();

await increment(5);
await createTask("Build feature", { priority: "high" });

const result = await analyze(data);
console.log(result.score);

try {
  await dangerousOperation();
} catch (e) {
  console.error("Failed:", e.message);
}
```

Types for parameters and return values come from your system class. TypeScript catches mismatches at compile time.

### Fire and Forget

For streaming operations, trigger the action without awaiting. The server streams chunks into state, and your component re-renders as that state updates:

```typescript theme={null}
function Chat() {
  const { response, generate } = useSystem<Assistant>();

  const handleSubmit = (prompt: string) => {
    generate(prompt);  // Don't await—state updates drive UI
  };

  return (
    <div>
      <button onClick={() => handleSubmit(input)} disabled={response.status === 'streaming'}>
        Send
      </button>
      <div>{response.current}</div>
    </div>
  );
}
```

## Connection Options

Pass options to configure the connection:

```typescript theme={null}
useSystem<MySystem>({
  id: "user-123",                    // Which instance to connect to
  onConnect: () => console.log("Connected"),
  onDisconnect: () => console.log("Disconnected"),
  onError: (e) => console.error(e),
  autoReconnect: true,               // Default: true
  reconnectDelay: 1000,              // Milliseconds between attempts
  maxReconnectAttempts: 10,
  auth: { token: sessionToken },     // Passed to server
});
```

### Instance IDs

The `id` option determines which system instance you connect to. Each unique ID corresponds to a separate Durable Object with isolated state:

```typescript theme={null}
const doc1 = useSystem<Editor>({ id: "doc-abc" });
const doc2 = useSystem<Editor>({ id: "doc-xyz" });
// Separate instances, separate state

const editorA = useSystem<Editor>({ id: "doc-abc" });
const editorB = useSystem<Editor>({ id: "doc-abc" });
// Same instance—collaborative editing
```

Use user IDs for per-user state, document IDs for per-document state, or any multi-tenant pattern.

## Connection Status

Handle different connection states in your UI:

```typescript theme={null}
function App() {
  const { count, status, error } = useSystem<MySystem>();

  if (status === 'connecting') return <LoadingSpinner />;
  if (status === 'error') return <p>Failed: {error?.message}</p>;
  if (status === 'disconnected') return <p>Reconnecting...</p>;

  return <MainUI count={count} />;
}
```

Status transitions: `'connecting'` → `'connected'` on success. If connection drops, `'disconnected'` while reconnecting. If reconnection fails, `'error'`.

## Multiple Systems

Connect to multiple systems in one component. Each creates an independent WebSocket:

```typescript theme={null}
function Dashboard() {
  const editor = useSystem<Editor>({ id: "doc-123" });
  const analytics = useSystem<Analytics>({ id: "global" });

  return (
    <div>
      <EditorPane {...editor} />
      <AnalyticsPanel {...analytics} />
    </div>
  );
}
```

## Type Generation

Types generate automatically during development:

```bash theme={null}
npx idyllic dev      # Watches and regenerates on changes
npx idyllic generate # One-time generation
```

Generated types appear at `.idyllic/types.ts`. Import your system type and pass it to `useSystem`:

```typescript theme={null}
import type { Editor } from '../systems/Editor';
const { content, improve } = useSystem<Editor>();
```

Types update as you modify your system class, keeping frontend and backend in sync.

***

## FAQ

### When does useSystem create vs connect?

The hook connects to an existing instance or creates one on-demand. There's no explicit "create"—instances materialize when the first client connects with a given ID.

### What happens when the component unmounts?

The WebSocket closes. If the user navigates back, a new connection opens and receives a fresh state snapshot. Server-side execution continues regardless of client connections.

### How do I share state between components?

Two options: (1) Use the same instance ID in multiple components—each gets its own connection but sees identical state. (2) Lift `useSystem` to a parent and pass state via props or context.

### How do I test components?

Mock the hook to provide controlled state and action spies:

```typescript theme={null}
vi.mock('@idyllic/react', () => ({
  useSystem: () => ({
    count: 5,
    tasks: [],
    increment: vi.fn(),
    status: 'connected',
  }),
}));
```

For integration tests, run `npx idyllic dev` and use actual connections.

***

<Card title="Guides" icon="arrow-right" href="/guides/multiple-streams">
  Learn patterns for building with Idyllic
</Card>
