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

# Blocks and references

> Define blocks, actions and views as Effects, write components that return an Effect, insert blocks with encoded state, and resolve your links with Effect handlers.

> Reintersect apps are in developer preview. Names and limits can still change before general
> release.

Blocks and references take the same options on the Effect API as on the Promise API, with Effect
Schema in place of Standard Schema. Read [Blocks](/blocks/overview) and
[References](/references/overview) for what each option does. This page shows the Effect side.

## Actions

`defineAction(name, options, handler)` defines an action a block can bind to a button, menu item or
form. Its options are the same as on the [Promise API](/blocks/state-and-actions#options): `args`,
`input` and `state` take Effect Schemas, and `inputs`, `destructive`, `readOnly`, `idempotent`,
`agent`, `description`, `scopes`, `env` and `config` are unchanged.

This action promotes a release when more members voted go than no-go, and fails with a tagged error
otherwise:

```ts src/promote.ts theme={null}
import { choice, defineAction } from "@reintersect/sdk/effect/app";
import { Clock, Schema } from "effect";
import { Deployments } from "./deployments";

export class Vetoed extends Schema.TaggedError<Vetoed>()("Vetoed", { nogo: Schema.Number }) {}

export const LaunchState = Schema.Struct({
  status: Schema.Literal("open", "shipped"),
  shippedAt: Schema.optional(Schema.DateFromString),
});

export const verdict = choice(["go", "nogo"]);

export const promote = defineAction(
  "promote",
  {
    args: Schema.Struct({ environment: Schema.Literal("staging", "production") }),
    state: LaunchState,
    inputs: { verdict },
    destructive: true,
    description: "Promote the release",
  },
  function* ({ args, tallies }) {
    if (tallies.verdict.nogo > tallies.verdict.go) {
      return yield* new Vetoed({ nogo: tallies.verdict.nogo });
    }

    const deployments = yield* Deployments;

    const url = yield* deployments.promote(args.environment);
    const now = yield* Clock.currentTimeMillis;

    return { state: { status: "shipped", shippedAt: new Date(now) }, notice: `Live at ${url}` };
  },
);
```

The action's type records `Deployments` as a service it needs and `Vetoed`, plus whatever
`deployments.promote` can fail with, as its errors. A failed action leaves the block as it was and
shows the member an error. To show your own message instead, turn the failure into a result; see
[Errors](/effect/errors#turn-a-failure-into-an-answer).

The handler returns the same [results](/blocks/state-and-actions#results) as on the Promise API:
`state`, `notice`, `fieldErrors`, `openUrl`, `final`, `notify`, `revalidate` and `options`.

## Blocks and views

`defineBlock(options)` takes the [block options](/blocks/overview) with Effect Schemas for `props`
and `state`. `.render(view)` takes a view, which can be a generator, a function returning an
`Effect` or a `Promise`, or a function returning JSX:

```tsx src/launch.tsx theme={null}
import { Actions, Block, Button, ChoiceGroup, Header, Text } from "@reintersect/sdk/components";
import { defineBlock } from "@reintersect/sdk/effect/app";
import { Schema } from "effect";
import { Deployments } from "./deployments";
import { LaunchState, promote, verdict } from "./promote";

export const Launch = defineBlock({
  type: "launch",
  title: "Launch",
  insertable: true,
  props: Schema.Struct({ title: Schema.String }),
  state: LaunchState,
  initialState: { status: "open" },
  inputs: { verdict },
  actions: { promote },
}).render(function* (props, { actions, inputs, state }) {
  const { owner } = yield* Deployments;

  return (
    <Block summary={`${props.title} is ${state.status}`}>
      <Header>{props.title}</Header>
      <Text>
        {state.shippedAt === undefined ? `Owned by ${owner}` : `Shipped ${state.shippedAt.toISOString()}`}
      </Text>
      <ChoiceGroup input={inputs.verdict} label="Verdict" labels={{ go: "Go", nogo: "No go" }} />
      <Actions>
        <Button
          label="Promote"
          action={actions.promote.with({ environment: "production" })}
          confirm={{ title: "Promote to production?" }}
        />
      </Actions>
    </Block>
  );
});
```

The view reads `state` decoded, so `state.shippedAt` is a `Date`. Services the view yields join the
services of its actions in the block's type.

### Components that return an Effect

A function component can return an `Effect` of JSX, so a part of a view can read a service on its
own:

```tsx theme={null}
import { Text } from "@reintersect/sdk/components";
import { Effect } from "effect";
import { Deployments } from "./deployments";

const OwnerName = () => Effect.map(Deployments, ({ owner }) => owner);

const Reviewer = () => (
  <Text>
    Reviewed by <OwnerName />
  </Text>
);
```

The default `"jsxImportSource": "@reintersect/sdk"` in `tsconfig.json` already accepts these
components. The services a component reads aren't tracked in any piece's type, so make sure the app
provides them, for example because a view or action of the same app yields them too.

### Inserting blocks

`Block.insert(props, { state })` builds the block for a command or message action answer. Pass
`state` decoded; the SDK encodes it with the block's schema:

```ts theme={null}
const insert = Launch.insert(
  { title: "Spring release" },
  { state: { status: "shipped", shippedAt: new Date("2026-10-14T09:00:00Z") } },
);
```

The [client](/effect/client-and-streams) does the same: it encodes the state you pass to
`blocks.update` and decodes the state it reads back.

## References

`defineReference(kind, options)` teaches Reintersect your links. `resolve` and `search` are Effect
handlers; the other options are the same as on the [Promise API](/references/resolving). This
example is the Effect template's, and its `Tasks` service has `find`, `search`, `add` and
`updateStatus`:

```ts src/taskReference.ts theme={null}
import { defineReference } from "@reintersect/sdk/effect/app";
import { Array, Option } from "effect";
import { InstallConfig } from "./schemas";
import { TaskBlock } from "./taskBlock";
import { Tasks } from "./tasks";

export const TaskLink = defineReference("task", {
  title: "Task",
  patterns: { task: "https://tasks.example.com/tasks/:id" },
  block: TaskBlock,
  config: InstallConfig,
  resolve: function* ({ config, params }) {
    const tasks = yield* Tasks;

    const task = yield* tasks.find(params.id);

    if (Option.isNone(task)) return { access: "notFound" };
    return {
      externalId: task.value.id,
      title: task.value.title,
      subtitle: `${config.project} ${task.value.id}`,
      block: {
        props: { id: task.value.id, title: task.value.title },
        state: { status: task.value.status },
      },
    };
  },
  search: function* ({ limit, query }) {
    const tasks = yield* Tasks;

    const found = yield* tasks.search(query, limit);

    return Array.map(found, (task) => ({
      url: `https://tasks.example.com/tasks/${task.id}`,
      title: task.title,
      subtitle: task.id,
    }));
  },
});
```

`params` is typed from the pattern, so `params.id` is a `string`. The block preview's `state` is
encoded with `TaskBlock`'s state schema before it's sent. Services that `resolve` and `search` yield
become requirements of the reference.

If `resolve` fails, the SDK logs the failure and answers `unavailable` for that URL. Reintersect
keeps the link as it is and tries again later; the other URLs in the batch are unaffected.

## Recipes

`@reintersect/sdk/effect/recipes` has every [recipe](/recipes/overview) as an Effect block, such as
`checklist({ type: "launch-checklist" })`. Add one to an Effect app the same way as your own blocks.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.