AvalonAvalon
GitHub

React

Using React components as islands in Avalon.

Setup

Add 'react' to the integrations array in your vite.config.ts:

const plugins = await avalon({
  integrations: ['react'],
});

Install the peer dependencies if not already present:

>_ Terminal
bun add react react-dom

File name

Name the file *.react.tsx. Avalon uses that extension to compile the file with React's JSX runtime. A /** @jsxImportSource react */ comment is optional and overrides the filename.

Writing a React island

// src/components/Counter.react.tsx
import { useState } from 'react';

export default function Counter({ initialCount = 0 }: { initialCount?: number }) {
  const [count, setCount] = useState(initialCount);
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}

Using the island

import Counter from '../components/Counter.react.tsx';

export default function Page() {
  return (
    <div>
      <Counter initialCount={5} island={{ condition: 'on:interaction' }} />
    </div>
  );
}

Server rendering

React islands are server-rendered to HTML by default using react-dom/server. The client bundle only loads when the hydration condition is met.

Hooks and context

All React hooks work inside islands. Context providers must live inside the island itself — they cannot span across multiple islands.

import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext('light');

export default function ThemedApp() {
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider value={theme}>
      <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
        Toggle theme
      </button>
    </ThemeContext.Provider>
  );
}

Caveats

  • Hooks work — useState, useEffect, useRef, useMemo, and all other React hooks work as expected inside islands.
  • No signals — React does not have a signals primitive. Use useState or useReducer for local state, and useContext for shared state within an island tree.
  • Context cannot span islands — A Context.Provider in one island cannot provide values to a different island. Each island is an independent React tree. For live updates across islands, use DOM CustomEvents; avoid module-level singleton stores that can leak on the server during SSR.