# Server Actions

> Turn procedures into React Server Actions that take typed input or FormData, with useActionState and optimistic updates.

Nothing here is Next-specific: it works with any React Server Components setup.

`experimental_createAction` from `@trpc12/trpc/actions` makes a factory that sets the ctx, layer and result mode once. `action(procedure)` is then an async function that takes the input or a form's `FormData`:

```ts
'use server';
import { experimental_createAction } from '@trpc12/trpc/actions';

const action = experimental_createAction({
  ctx: async () => ({ user: await auth() }),
});

export const createPost = action(appRouter.post.create);
```

```tsx
<form
  action={async (form) => {
    await createPost(form);
  }}
>
  <input name="title" />
  <input name="tags[]" value="news" />
</form>;

const result = await createPost({ title, tags: [] });
if (result.error) console.log(result.error.code, result.error.message);
else console.log(result.data.title);
```

- `FormData` is parsed with bracket notation (`tags[]=a`, `author[name]=ada`) and coerced from the input's JSON Schema (`'40'` becomes `40` where the schema wants a number). Framework fields such as Next's `$ACTION_ID_…` are dropped.
- `result: 'safe'` (the default) returns `{ data }` or `{ error }`. Both are plain objects, because React sends an action's result to the browser: there an `Error` arrives without its class or `data`, and an array without extra fields, so the result is not `safe()`'s `[data, error]` ([Errors without try/catch](https://v12.trpc.io/v12/docs/client/errors.md)). `error` holds the `TrpcError`'s `code`, `message`, `defined` and `data` (defined errors only), and narrows on `code` and `defined` the same way. `result: 'throw'` returns the data and throws `TrpcError`.
- Results reach the browser, so unexpected errors are masked outside development, as in handlers. `onError` sees them first.
- A factory whose ctx or layer can't satisfy a procedure is a type error, and so is wrapping a subscription.
- Next's `redirect()`, `notFound()` and dynamic-API errors thrown in a procedure or `ctx` are rethrown as they are, not returned as errors, so Next can act on them. Any error with a string `digest` counts. The same holds for `call()`, `createRouterClient()` and `localLink` ([Server-side calls](https://v12.trpc.io/v12/docs/server/server-side-calls.md)).

## Form state and optimistic updates

An action also takes React's `useActionState` call (the previous state, then the input or `FormData`), so React's own hooks give pending state and optimistic UI; there is no `useServerAction`:

```tsx
('use client');
import { useActionState, useOptimistic } from 'react';
import { addTitle } from './actions'; // action(appRouter.title.add)

function Titles() {
  const [result, submit, isPending] = useActionState(addTitle, undefined);
  const [shown, addShown] = useOptimistic(
    result?.data ?? [],
    (list: string[], title: string) => [...list, title],
  );
  return (
    <form
      action={(form) => {
        addShown(form.get('title') as string);
        submit(form);
      }}
    >
      <input name="title" />
      {shown.map((title) => (
        <p key={title}>{title}</p>
      ))}
      {isPending ? 'Saving…' : result?.error?.code}
    </form>
  );
}
```

- The state starts `undefined` and is then the last result (`{ data }` or `{ error }` with `result: 'safe'`). The previous state isn't sent to the procedure.
- The optimistic value shows until the action settles, then falls back to `result`.

## Direct calls

`experimental_createCallable` is the same factory for direct calls with typed input (no `FormData`), from server components, loaders or scripts. Its results stay on the server, so `result: 'safe'` returns `safe()`'s `[data, error]`, with the `TrpcError` itself:

```ts
const callable = experimental_createCallable({
  ctx: createContext,
  result: 'throw',
});
const post = await callable(appRouter.post.byId)({ id: '1' });
```

## Credits

Actions that take `FormData` follow [oRPC's form actions](https://orpc.dev/docs/integrations/next).
