Silgi
Integrations

React Server Actions

Call Silgi procedures as React Server Actions — type-safe [error, data] tuples instead of try/catch.

React Server Actions let you call server-side code directly from React components. Silgi wraps your procedures as actions that return [error, data] tuples instead of throwing, making error handling straightforward in your UI code.

This works with Next.js (App Router) and TanStack Start.

Prerequisites

You need a Silgi router defined on the server. If you haven't set one up yet, see the Getting Started guide.

createAction

Wraps a single procedure as a server action. Returns [error, data] instead of throwing.

Define the action in a file with "use server":

// app/actions.ts
'use server'
import {  } from 'silgi/react'
import {  } from '../router'

export const  = (.users.list)
export const  = (.users.create)

Use it in a component:

// app/page.tsx
import { listUsers } from './actions'

export default async function Page() {
  const [error, users] = await listUsers({ limit: 10 })

  if (error) {
    return <p>Error: {error.message}</p>
  }

  return (
    <ul>
      {users.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  )
}

The return type

Every action returns a tuple: [error, data].

  • On success: [null, data]error is null, data is the procedure's return value
  • On failure: [error, undefined]error has code, status, message, and optionally data

This pattern avoids try/catch in your components and makes error states explicit.

createActions

Converts an entire router into actions at once. The result mirrors your router structure:

'use server'
import {  } from 'silgi/react'
import {  } from '../router'

export const  = ()
// In a component or server function
const [, ] = await actions.users.list({ : 10 })
const [, ] = await actions.users.create({ : 'Alice' })

This is convenient when you have many procedures and don't want to export each one individually.

createFormAction

For HTML <form> submissions, createFormAction accepts FormData and converts it into the input object your procedure expects:

// app/actions.ts
'use server'
import {  } from 'silgi/react'

export const  = (appRouter.users.create)
<form action={submitUser}>
  <input name='name' required />
  <input name='email' type='email' required />
  <button type='submit'>Create User</button>
</form>

Nested form data

Bracket notation in field names is automatically parsed into nested objects:

<input name="user[name]" value="Alice" /> <input name="user[email]" value="[email protected]" />

Becomes:

{ "user": { "name": "Alice", "email": "[email protected]" } }

Array indices work too: items[0], items[1], etc.

Custom parsing

If the default FormData parser doesn't match your needs, provide your own:

const  = createFormAction(appRouter.users.create, {
  : () => ({
    : .get('name'),
    : .get('email'),
  }),
})

Framework errors

Silgi automatically detects and rethrows framework-specific errors:

  • Next.js redirect() and notFound() (errors with NEXT_ digest)
  • TanStack Router navigation errors (isNotFound: true)
  • Response objects (used for redirects)

These errors pass through untouched — they won't be caught as [error, data] tuples.

This means redirect("/login") inside a procedure works as expected in Next.js. The redirect happens, and the action doesn't return an error tuple.

useServerAction

A React hook that wraps a server action with loading and error state:

import { useServerAction } from 'silgi/react'

function CreateUserButton() {
  const { execute, data, error, isPending, reset } = useServerAction(createUser)

  return (
    <div>
      <button onClick={() => execute({ name: 'Alice' })} disabled={isPending}>
        {isPending ? 'Creating...' : 'Create User'}
      </button>
      {error && <p>Error: {error.message}</p>}
      {data && <p>Created: {data.name}</p>}
    </div>
  )
}

execute() returns the same [error, data] tuple as the action itself, so you can also handle results inline.

useOptimisticServerAction

Like useServerAction, but applies an optimistic update immediately while the server call is in flight:

import { useOptimisticServerAction } from 'silgi/react'

function UserName({ user }) {
  const { execute, displayData, isPending } = useOptimisticServerAction(updateUser, {
    optimistic: (input) => ({ ...user, ...input }),
  })

  return (
    <div>
      <span>{displayData?.name ?? user.name}</span>
      <button onClick={() => execute({ name: 'Bob' })}>Rename</button>
    </div>
  )
}
  • displayData shows the optimistic value while pending, then switches to the confirmed server response.
  • If the call fails, the optimistic value is rolled back automatically.

What's next?

  • Getting Started — set up a Silgi router
  • Typed Errors — how error maps and fail() work, and how they appear in the error tuple
  • TanStack Query — client-side data fetching with caching, as an alternative

On this page