# useController()

[Controller](https://dataclient.io/docs/api/Controller.md) provides type-safe methods to access and dispatch actions to the store.

For instance [fetch](https://dataclient.io/docs/api/Controller.md#fetch), [invalidate](https://dataclient.io/docs/api/Controller.md#invalidate),
and [setResponse](https://dataclient.io/docs/api/Controller.md#setResponse)

```tsx
import { useCallback } from 'react';
import { useController } from '@data-client/react';
import { MyResource } from './resources';

function MyComponent({ id }: { id: string }) {
  const ctrl = useController();

  const handleRefresh = useCallback(
    async e => {
      await ctrl.fetch(MyResource.get, { id });
    },
    [ctrl, id],
  );

  const handleSuspend = useCallback(
    async e => {
      await ctrl.invalidate(MyResource.get, { id });
    },
    [ctrl, id],
  );

  const handleLogout = useCallback(
    async e => {
      ctrl.resetEntireStore();
    },
    [ctrl],
  );
}
```

## Examples

### Form submission

[fetch](https://dataclient.io/docs/api/Controller.md#fetch) returns the denormalized response, matching [useSuspense()](https://dataclient.io/docs/api/useSuspense.md)'s return type. This allows using Entity methods like `pk()`.

```tsx
import type { FormEvent } from 'react';
import { useNavigate } from 'react-router';
import { useController } from '@data-client/react';
import { PostResource } from './PostResource';

function CreatePost() {
  const ctrl = useController();
  const navigate = useNavigate();

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    const post = await ctrl.fetch(
      PostResource.getList.push,
      new FormData(e.target as HTMLFormElement),
    );
    post.title;
    post.computedField;
    navigate(`/post/${post.pk()}`);
  };

  return <form onSubmit={handleSubmit}>{/* fields */}</form>;
}
```

### Direct entity update

Use [set](https://dataclient.io/docs/api/Controller.md#set) for immediate updates without network requests. Supports functional updates to avoid race conditions.

```tsx
import { useController } from '@data-client/react';
import { Article } from './Article';

function VoteButton({ articleId }: { articleId: string }) {
  const ctrl = useController();

  return (
    <button
      onClick={() =>
        ctrl.set(Article, { id: articleId }, article => ({
          ...article,
          votes: article.votes + 1,
        }))
      }
    >
      Vote
    </button>
  );
}
```

### Invalidate after mutation

Force refetch of related data using [invalidate](https://dataclient.io/docs/api/Controller.md#invalidate) or [expireAll](https://dataclient.io/docs/api/Controller.md#expireAll).

```tsx
import { useController } from '@data-client/react';
import { UserResource } from './UserResource';

function ClearUserCache({ userId }: { userId: string }) {
  const ctrl = useController();

  const handleClear = async () => {
    // invalidate() causes suspense; expireAll() refetches silently
    ctrl.expireAll(UserResource.get);
    ctrl.expireAll(UserResource.getList);
  };

  return <button onClick={handleClear}>Refresh user data</button>;
}
```

> **Tip**
>
> For better performance and consistency, prefer [including side effect updates in mutation responses](https://dataclient.io/rest/guides/side-effects.md).

### Prefetching

Use [fetchIfStale](https://dataclient.io/docs/api/Controller.md#fetchIfStale) to prefetch without overfetching fresh data.

```tsx
import { Link } from 'react-router';
import { useController } from '@data-client/react';
import { ArticleResource } from './ArticleResource';

function ArticleLink({ id }: { id: string }) {
  const ctrl = useController();

  return (
    <Link
      to={`/article/${id}`}
      onMouseEnter={() => ctrl.fetchIfStale(ArticleResource.get, { id })}
    >
      Read more
    </Link>
  );
}
```

### Websocket updates

Populate cache with external data via [set](https://dataclient.io/docs/api/Controller.md#set).

```tsx
import { useEffect } from 'react';
import { useController } from '@data-client/react';
import { EntityMap } from './resources';

function useWebsocket(url: string) {
  const ctrl = useController();

  useEffect(() => {
    const ws = new WebSocket(url);
    ws.onmessage = event => {
      const { entity, args, data } = JSON.parse(event.data);
      ctrl.set(EntityMap[entity], args, data);
    };
    return () => ws.close();
  }, [ctrl, url]);
}
```

> **Warning**
>
> For production use, implement a [Manager for data streams](https://dataclient.io/docs/concepts/managers.md#data-stream) rather than component-level effects. Managers handle connection lifecycle globally and work with SSR.

### Todo App

Example app: [todo-app](https://github.com/reactive/data-client/tree/master/examples/todo-app) ([`src/resources/TodoResource.ts`](https://github.com/reactive/data-client/blob/master/examples/todo-app/src/resources/TodoResource.ts), [`src/pages/Home/TodoListItem.tsx`](https://github.com/reactive/data-client/blob/master/examples/todo-app/src/pages/Home/TodoListItem.tsx))
