# Reactions bar

> Emoji reactions under a record with counts, which toggle the caller's own reaction.

- Block: [reactions](https://bettersupabase.com/blocks/reactions.md)
- Kind: component
- Data: loaded on the server through the block's client
- Page: https://bettersupabase.com/recipes/reactions-bar

## Install the block

```bash
npx better-supabase add reactions
```

## Web (Next.js)

Add the shadcn components:

```bash
npx shadcn add alert button card skeleton
```

From `apps/examples/nextjs/src/features/customers/components/reactions-bar.tsx` (https://github.com/ScaleDockHQ/better-supabase/blob/main/apps/examples/nextjs/src/features/customers/components/reactions-bar.tsx):

```tsx
const EMOJIS = ["👍", "🎉", "❤️", "👀", "🚀"] as const;

/**
 * Quick reactions on the customer. The counts load in the browser and update
 * when anyone in the organization reacts (the `reactions` topic).
 */
export function ReactionsBar({
  organizationId,
  customerId,
  canReact,
}: {
  organizationId: string;
  customerId: string;
  canReact: boolean;
}) {
  const t = useExtracted("customers");
  const [busy, setBusy] = useState<string | null>(null);

  const { reactions, error, toggle, refresh } = useReactions({
    organizationId,
    subjectType: "customer",
    subjectIds: [customerId],
    topic: blockConfig.reactions.options.topic,
    schema: blockConfig.reactions.api,
  });

  const counts = new Map(
    (reactions?.[customerId] ?? []).map((each) => [each.emoji, each]),
  );

  const total = [...counts.values()].reduce((sum, each) => sum + each.count, 0);

  return (
    <Card size="sm" data-recipe="reactions-bar">
      <CardHeader>
        <CardTitle>{t("Reactions")}</CardTitle>
      </CardHeader>
      <CardContent className="flex flex-col gap-3">
        {error ? (
          <Alert variant="destructive">
            <TriangleAlertIcon />
            <AlertTitle>{t("The reactions didn't load")}</AlertTitle>
            <AlertDescription>{t("Try again in a moment.")}</AlertDescription>
            <AlertAction>
              <Button
                size="sm"
                variant="outline"
                onClick={() => {
                  void refresh();
                }}
              >
                {t("Retry")}
              </Button>
            </AlertAction>
          </Alert>
        ) : null}
        {reactions === undefined ? (
          error ? null : (
            <div className="flex gap-2">
              {EMOJIS.map((emoji) => (
                <Skeleton key={emoji} className="h-8 w-12" />
              ))}
            </div>
          )
        ) : (
          <div className="flex flex-col gap-2">
            <fieldset className="flex flex-wrap gap-2">
              <legend className="sr-only">{t("React to this customer")}</legend>
              {EMOJIS.map((emoji) => {
                const count = counts.get(emoji);

                return (
                  <Button
                    key={emoji}
                    size="sm"
                    variant={count?.reacted ? "secondary" : "outline"}
                    aria-pressed={count?.reacted ?? false}
                    disabled={!canReact || busy !== null}
                    onClick={() => {
                      setBusy(emoji);
                      void toggle(customerId, emoji).finally(() => {
                        setBusy(null);
                      });
                    }}
                  >
                    <span aria-hidden>{emoji}</span>
                    <span className="tabular-nums">{count?.count ?? 0}</span>
                  </Button>
                );
              })}
            </fieldset>
            {total === 0 ? (
              <p className="text-xs text-muted-foreground">
                {canReact
                  ? t("No reactions yet. Pick one to be the first.")
                  : t("No reactions yet.")}
              </p>
            ) : null}
          </div>
        )}
      </CardContent>
    </Card>
  );
}
```

## Prompt

Paste this into a coding agent to build the recipe in your app.

### Web

```text
Build "Reactions bar" in a React web app that uses shadcn/ui components. Emoji reactions under a record with counts, which toggle the caller's own reaction.

It uses the better-supabase `reactions` block (Reactions). It loads its data on the server through the block's client.

Steps:

1. If the app doesn't have the block yet, add the SQL with `npx better-supabase add reactions` and apply the migration.
2. Add the shadcn components the recipe uses: `npx shadcn add alert button card skeleton`.
3. The recipe needs no glue: it calls the block's client on the server.
4. Write the component. Load the data on the server with the block's client, starting from the recipe's snippet (`apps/examples/nextjs/src/features/customers/components/reactions-bar.tsx`). Adapt it to the app's existing components, styling and data loading instead of copying it as is.
5. Check the result against the recipe and the block's docs: it typechecks, it reads the same block data, and it handles the loading and empty states the snippet handles.

Read the full code first:

- Recipe: https://bettersupabase.com/recipes/reactions-bar.md
- Block docs: https://bettersupabase.com/docs/blocks/reactions
```
