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

@varbyte/boxstore-astro

v0.1.0

Published

Astro integration for @varbyte/boxstore with React and Preact support

Readme

@varbyte/boxstore-astro

Astro integration for boxstore - enabling signal-based state management across React and Preact islands in Astro applications.

Features

  • Cross-island state sharing - Module-level singleton stores work across all islands in a page
  • Framework flexibility - Use React or Preact hooks via subpath exports
  • Optional Context API - StoreProvider for intra-island React Context when needed
  • Type-safe - Full TypeScript support with type inference
  • SSR-ready - Works with Astro's server-side rendering
  • Tiny - Minimal bundle size impact

Installation

npm install @varbyte/boxstore-astro @varbyte/boxstore @varbyte/signals-core astro

For React islands:

npm install react react-dom @varbyte/boxstore-react

For Preact islands:

npm install preact @varbyte/boxstore-preact

Requirements

  • Astro >= 4.0.0
  • @varbyte/boxstore >= 0.1.0
  • @varbyte/signals-core >= 1.0.0
  • React >= 18.0.0 (for React islands)
  • Preact >= 10.0.0 (for Preact islands)

Quick Start

1. Create a store module

// src/stores/counter.ts
import { createStore } from '@varbyte/boxstore'

export const counterStore = createStore({
  state: {
    count: 0
  },
  actions: {
    increment() {
      this.state.count.update(n => n + 1)
    },
    decrement() {
      this.state.count.update(n => n - 1)
    }
  }
})

2. Use in React islands

// src/components/Counter.tsx
import { useSelector } from '@varbyte/boxstore-astro/react'
import { counterStore } from '../stores/counter'

export function Counter() {
  const count = useSelector(counterStore, s => s.state.count())
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => counterStore.increment()}>+</button>
      <button onClick={() => counterStore.decrement()}>-</button>
    </div>
  )
}

3. Use in Preact islands

// src/components/Display.tsx
import { useSelector } from '@varbyte/boxstore-astro/preact'
import { counterStore } from '../stores/counter'

export function Display() {
  const count = useSelector(counterStore, s => s.state.count())
  
  return <div>Current count: {count}</div>
}

4. Add islands to Astro page

---
// src/pages/index.astro
import { Counter } from '../components/Counter'
import { Display } from '../components/Display'
---

<html>
  <body>
    <h1>Boxstore Astro Demo</h1>
    
    <!-- React island -->
    <Counter client:load />
    
    <!-- Preact island -->
    <Display client:load />
    
    <!-- Both islands share the same store state! -->
  </body>
</html>

Architecture: Cross-Island State Sharing

Astro islands are independently hydrated and do NOT share React/Preact context trees. To share state across islands, use module-level singleton stores:

// src/stores/app.ts
import { createStore } from '@varbyte/boxstore'

// This store instance is shared across all islands that import it
export const appStore = createStore({
  state: { theme: 'light' },
  actions: {}
})

When multiple islands import appStore, they all get the same instance because ES modules are singletons within the same page bundle.

API Reference

Subpath Exports

@varbyte/boxstore-astro/react

Re-exports hooks from @varbyte/boxstore-react for use in React islands.

import { useSelector, useStore } from '@varbyte/boxstore-astro/react'

Exports:

  • useSelector(store, selector) - Subscribe to selected state slice
  • useStore(store) - Subscribe to entire store
  • Types: Selector<S, A, R>, AnyStore

See @varbyte/boxstore-react documentation for detailed API.

@varbyte/boxstore-astro/preact

Re-exports hooks from @varbyte/boxstore-preact for use in Preact islands.

import { useSelector, useStore } from '@varbyte/boxstore-astro/preact'

Exports:

  • useSelector(store, selector) - Subscribe to selected state slice
  • useStore(store) - Subscribe to entire store
  • Types: Selector<S, A, R>, AnyStore

See @varbyte/boxstore-preact documentation for detailed API.

Main Export: StoreProvider

Optional React Context provider for intra-island store injection. Use this when multiple components within a single island need access to a store via context.

Important: StoreProvider only works within a single island. It does NOT share state across multiple islands. For cross-island sharing, use module-level stores.

import { StoreProvider, useStoreContext } from '@varbyte/boxstore-astro'

StoreProvider<S, A>

Provides a store via React Context to descendant components.

Props:

  • store: Store<S, A> - The boxstore instance to provide
  • children: React.ReactNode - Child components

Example:

// src/components/Island.tsx
import { StoreProvider } from '@varbyte/boxstore-astro'
import { useSelector } from '@varbyte/boxstore-astro/react'
import { createStore } from '@varbyte/boxstore'

const localStore = createStore({
  state: { message: 'Hello' },
  actions: {}
})

function ChildComponent() {
  const store = useStoreContext()
  const message = useSelector(store, s => s.state.message())
  return <p>{message}</p>
}

export function Island() {
  return (
    <StoreProvider store={localStore}>
      <ChildComponent />
    </StoreProvider>
  )
}

useStoreContext<S, A>()

Retrieves the store from the nearest StoreProvider ancestor.

Returns: Store<S, A> - The store instance from context

Throws: Error if used outside of a StoreProvider

Example:

import { useStoreContext } from '@varbyte/boxstore-astro'
import { useSelector } from '@varbyte/boxstore-astro/react'

function Child() {
  const store = useStoreContext()
  const value = useSelector(store, s => s.state.value())
  return <div>{value}</div>
}

Usage Patterns

Pattern 1: Module-Level Singleton (Recommended)

Best for cross-island state sharing.

// src/stores/user.ts
import { createStore } from '@varbyte/boxstore'

export const userStore = createStore({
  state: {
    name: 'Guest',
    isAuthenticated: false
  },
  actions: {
    login(name: string) {
      this.state.name.set(name)
      this.state.isAuthenticated.set(true)
    },
    logout() {
      this.state.name.set('Guest')
      this.state.isAuthenticated.set(false)
    }
  }
})
// Any island can import and use it
import { useSelector } from '@varbyte/boxstore-astro/react'
import { userStore } from '../stores/user'

export function UserBadge() {
  const name = useSelector(userStore, s => s.state.name())
  return <span>Welcome, {name}</span>
}

Pattern 2: StoreProvider for Local State

Use when a store is local to a single island and you want context-based injection.

// src/components/TodoIsland.tsx
import { createStore } from '@varbyte/boxstore'
import { StoreProvider, useStoreContext } from '@varbyte/boxstore-astro'
import { useSelector } from '@varbyte/boxstore-astro/react'

const todoStore = createStore({
  state: { todos: [] },
  actions: {
    addTodo(text: string) {
      this.state.todos.update(todos => [...todos, { id: Date.now(), text }])
    }
  }
})

function TodoList() {
  const store = useStoreContext()
  const todos = useSelector(store, s => s.state.todos())
  return (
    <ul>
      {todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
    </ul>
  )
}

function AddTodo() {
  const store = useStoreContext()
  return (
    <button onClick={() => store.addTodo('New task')}>
      Add Todo
    </button>
  )
}

export function TodoIsland() {
  return (
    <StoreProvider store={todoStore}>
      <TodoList />
      <AddTodo />
    </StoreProvider>
  )
}

Pattern 3: Mixed React and Preact Islands

Share state between React and Preact islands using a module-level store.

// src/stores/theme.ts
import { createStore } from '@varbyte/boxstore'

export const themeStore = createStore({
  state: {
    mode: 'light' as 'light' | 'dark'
  },
  actions: {
    toggle() {
      this.state.mode.update(m => m === 'light' ? 'dark' : 'light')
    }
  }
})
// src/components/ThemeToggle.tsx (React)
import { useSelector } from '@varbyte/boxstore-astro/react'
import { themeStore } from '../stores/theme'

export function ThemeToggle() {
  const mode = useSelector(themeStore, s => s.state.mode())
  
  return (
    <button onClick={() => themeStore.toggle()}>
      Current: {mode}
    </button>
  )
}
// src/components/ThemeDisplay.tsx (Preact)
import { useSelector } from '@varbyte/boxstore-astro/preact'
import { themeStore } from '../stores/theme'

export function ThemeDisplay() {
  const mode = useSelector(themeStore, s => s.state.mode())
  return <div>Theme is {mode}</div>
}

Both islands will stay in sync because they share the same themeStore instance.

Best Practices

1. Prefer Module-Level Stores

For most use cases, create stores at module level rather than using StoreProvider:

// ✅ Good - module-level singleton
export const store = createStore({ ... })

// ❌ Avoid - creating new store in component (unless truly local)
function Component() {
  const store = createStore({ ... }) // New instance every render!
  // ...
}

2. Use Granular Selectors

Select only the state you need to minimize re-renders:

// ✅ Good - only re-renders when count changes
const count = useSelector(store, s => s.state.count())

// ❌ Avoid - re-renders when any state changes
const state = useStore(store)
const count = state.count

3. Choose the Right Subpath Export

Import from the correct subpath for your island's framework:

// For React islands
import { useSelector } from '@varbyte/boxstore-astro/react'

// For Preact islands
import { useSelector } from '@varbyte/boxstore-astro/preact'

4. Type Your Stores

Define explicit types for better TypeScript inference:

interface UserState {
  name: string
  email: string
}

interface UserActions {
  updateEmail(email: string): void
}

export const userStore = createStore<UserState, UserActions>({
  state: {
    name: 'Guest',
    email: ''
  },
  actions: {
    updateEmail(email) {
      this.state.email.set(email)
    }
  }
})

TypeScript Support

All exports are fully typed. TypeScript will infer return types from your selectors:

const store = createStore({
  state: {
    count: 0,
    user: { name: 'Alice', age: 30 }
  },
  actions: {}
})

// TypeScript infers: const count: number
const count = useSelector(store, s => s.state.count())

// TypeScript infers: const name: string
const name = useSelector(store, s => s.state.user().name)

Troubleshooting

Store state not syncing between islands

Problem: Changes in one island don't reflect in another.

Solution: Ensure both islands import the store from the same module path. ES module identity must match:

// ✅ Both import from same path
import { store } from '../stores/app'

// ❌ Different relative paths might resolve to different modules
import { store } from '../../stores/app'

"useStoreContext must be used within StoreProvider"

Problem: Calling useStoreContext() outside of a StoreProvider.

Solution: Either wrap your component in StoreProvider or import the store directly:

// Option 1: Use StoreProvider
<StoreProvider store={myStore}>
  <Component />
</StoreProvider>

// Option 2: Import store directly (preferred for most cases)
import { myStore } from '../stores/my-store'
const value = useSelector(myStore, s => s.state.value())

Module not found errors

Problem: Cannot find module '@varbyte/boxstore-astro/react'

Solution: Ensure you've installed the corresponding adapter package:

npm install @varbyte/boxstore-react  # for /react subpath
npm install @varbyte/boxstore-preact # for /preact subpath

Examples

See the examples directory for complete working demos including:

  • Counter with mixed React/Preact islands
  • Theme switcher across islands
  • Todo list with local state
  • User authentication flow

License

MIT

Related Packages