npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@archipelago-js/react

v0.11.0

Published

React bindings for Archipelago islands

Readme

@archipelago-js/react

React bindings for Archipelago islands — server-rendered micro-frontends powered by Rails and React.

Install

yarn add @archipelago-js/react @archipelago-js/client react react-dom

60-Second Quick Start

1. Rails View Helper

Render an island from any ERB template:

<%= archipelago_island("TeamMembers", props: { team_id: @team.id }, stream: "team:#{@team.id}") %>

2. React Island Component

Create app/javascript/islands/TeamMembers.tsx:

import { useIslandProps, useIslandForm } from "@archipelago-js/react"

export default function TeamMembers() {
  const { props } = useIslandProps()
  const form = useIslandForm({ initialData: { name: "" } })

  return (
    <div>
      <h2>Team #{props.team_id}</h2>
      <input
        value={form.data.name}
        onChange={(e) => form.setData("name", e.target.value)}
      />
      <button onClick={() => form.post("add_member")} disabled={form.processing}>
        {form.recentlySuccessful ? "Added!" : "Add Member"}
      </button>
      {form.errors.name?.map((e) => <p key={e}>{e}</p>)}
    </div>
  )
}

3. Boot Islands

In your JS entry file:

import { bootArchipelagoIslands } from "@archipelago-js/react"
import TeamMembers from "../islands/TeamMembers"

void bootArchipelagoIslands({
  TeamMembers
})

Context

Every island is wrapped in an IslandProvider that provides context via useIslandContext():

| Field | Type | Description | |-------------|--------------------------------|---------------------------------------------| | component | string | Island component name | | params | Record<string, unknown> | Server-side params embedded in the HTML | | instance | string \| undefined | Optional instance identifier | | stream | string \| undefined | ActionCable stream name for live updates | | state | { props, version } | Current props and version | | setState | Dispatch<SetStateAction<…>> | Update island state |

useIslandProps() is the primary way to read props:

const { props, setProps, version } = useIslandProps()

Forms

useIslandForm (Controlled)

The form hook manages data, errors, processing state, and submission:

const form = useIslandForm({
  initialData: { email: "", role: "member" },
  clearFieldErrorsOnChange: true,
  recentlySuccessfulDuration: 2000,
  transform: (data) => ({ ...data, email: data.email.toLowerCase() }),
  onSuccess: (response) => console.log("Saved!", response),
  onError: (response) => console.log("Validation failed", response),
  onFinish: () => console.log("Request complete")
})

Returned values:

| Property | Type | Description | |------------------------|-----------------------------------------|--------------------------------------------------| | data | TData | Current form data | | setData(field, val) | (field, value) => void | Set a field value | | errors | Record<string, string[]> | Field-keyed validation errors | | setError(field, msg) | (field, message) => void | Manually set a field error | | clearErrors(...f?) | (...fields?) => void | Clear all or specific errors | | processing | boolean | True while request is in flight | | wasSuccessful | boolean | True after an ok/redirect response | | recentlySuccessful | boolean | True for recentlySuccessfulDuration ms after success | | progress | UploadProgress \| null | Upload progress ({ percentage, loaded, total }) | | transportError | Error \| null | Network/parse error (not a validation error) | | defaults(...) | Getter/setter for default values | defaults() returns defaults; defaults(next) updates them | | reset(...fields?) | (...fields?) => void | Reset all or specific fields to defaults | | resetAndClearErrors | (...fields?) => void | Reset data + clear errors | | post(op, overrides?) | (operation, overrides?) => Promise | Submit with POST | | put / patch / delete | Same as post | Submit with PUT / PATCH / DELETE |

<IslandForm> (Declarative / Uncontrolled)

For simple forms that don't need keystroke-level control:

import { IslandForm } from "@archipelago-js/react"

<IslandForm operation="update_settings" method="patch" resetOnSuccess>
  <input name="display_name" defaultValue={props.display_name} />
  <button type="submit">Save</button>
</IslandForm>

Render-prop children get access to the full form object:

<IslandForm operation="create_post" method="post">
  {(form) => (
    <>
      <input name="title" />
      {form.errors.title?.map((e) => <span key={e}>{e}</span>)}
      <button type="submit" disabled={form.processing}>
        {form.recentlySuccessful ? "Created!" : "Create"}
      </button>
    </>
  )}
</IslandForm>

Rails-style nested params are supported for common bracket patterns. Input names with brackets are parsed into nested objects:

<IslandForm operation="update_profile" method="patch">
  <input name="user[email]" defaultValue={props.email} />
  <input name="user[name]" defaultValue={props.name} />
  <input name="tags[]" defaultValue="react" />
  <input name="tags[]" defaultValue="rails" />
  <button type="submit">Save</button>
</IslandForm>
// submits: { user: { email: "...", name: "..." }, tags: ["react", "rails"] }

Supported patterns: field[key] (nested object), field[] (array append), and field[key][nested] (deep nesting). This covers the most common Rails form conventions. Exotic Rack edge cases (e.g. mixing numeric indices with array append on the same key) may not match Rack's behavior exactly.

Props:

| Prop | Type | Default | |------------------------|--------------------------|-----------| | operation | string | required | | method | FormMethod | "post" | | transform | (data) => data | — | | resetOnSuccess | boolean | false | | clearErrorsOnSuccess | boolean | false | | fixedParams | Record<string, unknown>| — | | onSuccess | (response) => void | — | | onError | (response) => void | — | | onForbidden | (response) => void | — | | onFinish | (response?) => void | — |

Streams (Live Updates via ActionCable)

Islands with a stream attribute automatically subscribe to an ActionCable channel. When a broadcast arrives, useIslandProps() updates the props in real time.

Setup

Assign an ActionCable consumer globally:

import { createConsumer } from "@rails/actioncable"
window.Archipelago = { cable: createConsumer() }

Server Broadcast

Archipelago.broadcast("team:#{team.id}", props: { members: team.members.as_json })

Custom Merge Logic

Use onLiveProps to merge incoming props with the current state:

const { props } = useIslandProps({
  onLiveProps: (next, previous) => ({
    ...previous,
    members: next.members
  })
})

Lazy Islands (defineIslandLoader)

Code-split islands so their JavaScript is only fetched when the island appears in the DOM:

import { defineIslandLoader, bootArchipelagoIslands } from "@archipelago-js/react"

void bootArchipelagoIslands({
  TeamMembers: defineIslandLoader(
    () => import("../islands/TeamMembers"),
    <div>Loading team...</div>  // optional fallback
  )
})

The Rails generator supports --lazy_registry to scaffold a lazy registry automatically:

bin/rails generate archipelago:install:react --lazy_registry

Turbo Lifecycle

Archipelago integrates seamlessly with Turbo Drive and Turbo Frames:

  • turbo:load / turbo:render / turbo:frame-load — Automatically boots any new [data-island] elements added to the page.
  • turbo:before-cache — All mounted islands are unmounted before Turbo caches the page, preventing stale React trees from persisting in the snapshot.
  • MutationObserver — A DOM observer detects dynamically inserted islands (e.g. from Turbo Streams or manual DOM manipulation) and boots them automatically.

No extra configuration is needed. Just call bootArchipelagoIslands(registry) once in your entry file.

Error Handling

FORM_ERROR

The @archipelago-js/client package exports a FORM_ERROR constant ("_base") for base-level form errors:

import { FORM_ERROR } from "@archipelago-js/client"

{form.errors[FORM_ERROR]?.map((e) => <p key={e}>{e}</p>)}

ArchipelagoTransportError

Network failures, HTML responses, and JSON parse errors are wrapped in ArchipelagoTransportError (also exported from @archipelago-js/client). The form hook captures these into form.transportError.

ErrorBoundary

Each island is wrapped in an ErrorBoundary. If an island crashes, the error is contained and other islands continue to function.

API Reference

| Export | Package | Description | |-------------------------------|----------|------------------------------------------| | bootArchipelagoIslands | react | Mount all [data-island] elements | | unmountArchipelagoIslands | react | Tear down all mounted islands | | defineIslandLoader | react | Create a lazy-loaded registry entry | | useIslandProps | react | Read/subscribe to island props | | useIslandForm | react | Controlled form state and submission | | IslandForm | react | Declarative form component | | IslandProvider | react | Context provider (used internally) | | useIslandContext | react | Access raw island context | | ErrorBoundary | react | Per-island error boundary | | islandFetch | client | Low-level island RPC call | | FORM_ERROR | client | "_base" constant for base errors | | ArchipelagoTransportError | client | Typed transport error class | | parseIslandResponse | client | Parse raw JSON into typed response |

Stability

This library follows Semantic Versioning. The public API — bootArchipelagoIslands, useIslandProps, useIslandForm, IslandForm, defineIslandLoader, ErrorBoundary, and useIslandContext — is considered stable. The discriminated response types (ok / error / redirect / forbidden), FORM_ERROR = "_base", and stream terminology are part of the public contract.

IslandForm bracket param parsing supports common Rails conventions (field[key], field[], field[key][nested]). Exotic Rack edge cases are not guaranteed.