Skip to content

React SDK

The hyperstack-react SDK provides hooks and providers for building live Solana applications with React. It’s built on top of hyperstack-typescript and adds React-specific features like automatic re-rendering, connection state management, and data transformation.


import { HyperstackProvider, useHyperstack } from "hyperstack-react";
import { ORE_STREAM_STACK } from "hyperstack-stacks/ore";
function App() {
return (
<HyperstackProvider>
<Dashboard />
</HyperstackProvider>
);
}
function Dashboard() {
const { views, isConnected } = useHyperstack(ORE_STREAM_STACK);
const { data: rounds, isLoading } = views.OreRound.latest.use();
if (isLoading) return <p>Loading...</p>;
return (
<div>
<p>{isConnected ? "🟢 Live" : "Connecting..."}</p>
<ul>
{rounds?.map((round) => (
<li key={round.id?.round_id}>Round #{round.id?.round_id}</li>
))}
</ul>
</div>
);
}

Terminal window
npm install hyperstack-react zustand

The SDK requires:

  • React (v19.0.0+)
  • Zustand (v4.0.0+) — Used for internal state management

src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { HyperstackProvider } from "hyperstack-react";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<HyperstackProvider>
<App />
</HyperstackProvider>
</React.StrictMode>,
);

The provider manages connections to stacks. Each stack definition includes its own URL, so the provider doesn’t need a URL prop.

PropTypeDefaultDescription
websocketUrlstring-Override URL for all stacks (optional)
autoConnectbooleantrueAuto-connect on mount
maxEntriesPerViewnumber | null10000Max entries per view before LRU eviction

You have two options for stack definitions:

Option A: Use curated Hyperstack feeds

Install the hyperstack-stacks package for pre-built, typed definitions of popular Solana programs:

Terminal window
npm install hyperstack-stacks
import { ORE_STREAM_STACK } from "hyperstack-stacks/ore";

Each stack includes its default URL (e.g., wss://ore.stack.usehyperstack.com).

Option B: Generate from your own stack

If you’ve built your own stack, generate a typed SDK using the CLI:

Terminal window
hs sdk create typescript my-stack
import { MY_STACK } from "./stack";

See CLI Commands for all hs sdk create options.


Access your stack’s typed interface:

import { useHyperstack } from "hyperstack-react";
import { ORE_STREAM_STACK } from "hyperstack-stacks/ore";
function RoundList() {
const { views } = useHyperstack(ORE_STREAM_STACK);
const { data: rounds, isLoading, error } = views.OreRound.list.use();
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
{rounds?.map((round) => (
<div key={round.id?.round_id}>
Round #{round.id?.round_id}{round.state?.motherlode}
</div>
))}
</div>
);
}
function RoundList() {
const { views } = useHyperstack(ORE_STREAM_STACK);
const { data: rounds, isLoading, error } = views.OreRound.list.use();
if (isLoading) return <div>Connecting...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{rounds?.map((round) => (
<li key={round.id?.round_id}>
Round #{round.id?.round_id} — Motherlode: {round.state?.motherlode}
</li>
))}
</ul>
);
}
function RoundDetail({ roundAddress }: { roundAddress: string }) {
const { views } = useHyperstack(ORE_STREAM_STACK);
const { data: round, isLoading } = views.OreRound.state.use({
key: roundAddress,
});
if (isLoading) return <div>Loading...</div>;
if (!round) return <div>Round not found</div>;
return (
<div>
<h2>Round #{round.id?.round_id}</h2>
<p>Motherlode: {round.state?.motherlode}</p>
<p>Total Deployed: {round.state?.total_deployed}</p>
</div>
);
}

const { views } = useHyperstack(MY_STACK);
// Get only one item with type-safe return (T | undefined instead of T[])
const { data: latestToken } = views.tokens.list.use({ take: 1 });
// data: Token | undefined
// Or use the dedicated useOne method
const { data: latestToken } = views.tokens.list.useOne();
// data: Token | undefined
// useOne with filters
const { data: topToken } = views.tokens.list.useOne({
where: { volume: { gte: 10000 } },
});

The SDK supports both server-side and client-side filtering:

Server-side options reduce data sent over the wire:

const { views } = useHyperstack(ORE_STREAM_STACK);
const { data: rounds } = views.OreRound.list.use({
take: 10, // Limit to 10 entities from server
skip: 20, // Skip first 20 (for pagination)
});

For advanced server-side filtering, use custom views defined in your stack.

Client-side filtering happens after data is received. Use where with comparison operators:

const { views } = useHyperstack(ORE_STREAM_STACK);
const { data: highValueRounds } = views.OreRound.list.use({
where: {
// Supported operators: gte, lte, gt, lt, or exact match
motherlode: { gte: 1000000 }, // Greater than or equal
difficulty: { lt: 50 }, // Less than
},
limit: 10, // Keep only first 10 matching results
});
OperatorDescription
gteGreater than or equal
gtGreater than
lteLess than or equal
ltLess than
(value)Exact match
// Exact match example
const { views } = useHyperstack(ORE_STREAM_STACK);
const { data } = views.OreRound.list.use({
where: { status: "active" }, // Exact equality
});
const { views } = useHyperstack(ORE_STREAM_STACK);
// Only subscribe when we have a valid address
const { data: round } = views.OreRound.state.use(
{ key: roundAddress },
{ enabled: !!roundAddress },
);
const { views } = useHyperstack(ORE_STREAM_STACK);
// Show placeholder while connecting
const { data: rounds } = views.OreRound.list.use({}, { initialData: [] });

The .use() method accepts two arguments: params and options.

ParamTypeSideDescription
takenumberServerLimit entities returned from server
skipnumberServerSkip first N entities (pagination)
whereobjectClientFilter with comparison operators
limitnumberClientMax results to keep after filtering
const { views } = useHyperstack(ORE_STREAM_STACK);
const { data } = views.OreRound.list.use({
take: 50, // Server sends max 50
skip: 0, // Start from beginning
where: { motherlode: { gte: 100000 } }, // Filter locally
limit: 10, // Keep first 10 matches
});
const { views } = useHyperstack(MY_STACK);
const { data } = views.tokens.list.use(
{ limit: 10 }, // Params
{
enabled: true, // Enable/disable subscription
initialData: [], // Initial data before first response
},
);
PropertyTypeDescription
dataT | T[] | undefinedCurrent view data
isLoadingbooleanTrue until first data received
errorError | undefinedSubscription error if any
refresh() => voidManually trigger refresh

The useHyperstack hook returns connection state directly:

import { useHyperstack } from "hyperstack-react";
import { ORE_STREAM_STACK } from "hyperstack-stacks/ore";
function ConnectionStatus() {
const { connectionState, isConnected } = useHyperstack(ORE_STREAM_STACK);
const statusColors = {
connected: "green",
connecting: "yellow",
reconnecting: "orange",
disconnected: "gray",
error: "red",
};
return (
<div style={{ color: statusColors[connectionState] }}>
{isConnected ? "Live" : connectionState}
</div>
);
}
PropertyTypeDescription
viewsobjectTyped view accessors
connectionStateConnectionStateCurrent WebSocket state
isConnectedbooleanConvenience: true when state === 'connected'
isLoadingbooleantrue until client is ready
errorError | nullConnection error if any
clientHyperStackLow-level client instance
StateDescription
disconnectedNot connected
connectingEstablishing connection
connectedActive and healthy
reconnectingAuto-reconnecting after failure
errorConnection failed

For cases where you need connection state outside of a component using useHyperstack, you can use the standalone hook:

import { useConnectionState } from "hyperstack-react";
function GlobalConnectionIndicator() {
const state = useConnectionState();
return <div>{state}</div>;
}

Returns a typed interface to your stack’s views, along with connection state.

const { views, connectionState, isConnected, isLoading, error, client } = useHyperstack(ORE_STREAM_STACK);
// Access views
views.OreRound.list.use()
views.OreRound.state.use({ key })
views.OreRound.latest.use()
// Check connection
if (isConnected) {
console.log('Live!');
}
OptionTypeDescription
urlstringOverride the stack’s default URL
// Connect to local development server
const { views, isConnected } = useHyperstack(ORE_STREAM_STACK, {
url: "ws://localhost:8878"
});

Standalone hook for connection state. Prefer using connectionState from useHyperstack when possible.

// Without argument: returns state of single active client
const state = useConnectionState();
// With stack: returns state for specific stack
const state = useConnectionState(ORE_STREAM_STACK);
// Returns: 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error'

Wrap your application to initialize the SDK:

<HyperstackProvider
autoConnect={true}
maxEntriesPerView={10000}
>
<YourApp />
</HyperstackProvider>

To override the URL for all stacks (e.g., for local development):

<HyperstackProvider websocketUrl="ws://localhost:8878">
<YourApp />
</HyperstackProvider>

A convenience method for fetching a single item from a list view with proper typing.

const { views } = useHyperstack(MY_STACK);
const { data } = views.tokens.list.useOne();
// data: Token | undefined (not Token[])

Equivalent to .use({ take: 1 }) but with a cleaner API and explicit intent.


A full React component combining everything:

src/App.tsx
import { useHyperstack } from "hyperstack-react";
import { ORE_STREAM_STACK } from "hyperstack-stacks/ore";
function OreDashboard() {
const { views, connectionState, isConnected } = useHyperstack(ORE_STREAM_STACK);
const {
data: rounds,
isLoading,
error,
} = views.OreRound.latest.use({
take: 5,
});
return (
<div className="app">
<header>
<h1>Live ORE Mining Rounds</h1>
<span className={`status ${connectionState}`}>
{isConnected ? "Live" : connectionState}
</span>
</header>
<main>
{isLoading && <p>Connecting to stream...</p>}
{error && <p className="error">Error: {error.message}</p>}
{rounds && (
<>
<p>{rounds.length} rounds streaming</p>
<div className="round-grid">
{rounds.map((round) => (
<div key={round.id?.round_id} className="round-card">
<h3>Round #{round.id?.round_id}</h3>
<p className="motherlode">{round.state?.motherlode}</p>
<p className="deployed">
Total Deployed: {round.state?.total_deployed}
</p>
</div>
))}
</div>
</>
)}
</main>
</div>
);
}
export default OreDashboard;

Every stack ships with Zod schemas that you can use to filter entities at the hook level. Pass a schema to .use() or .useOne() — entities that fail validation are silently excluded from results.

Use the “Completed” schema variant to only render entities where all fields are present:

import { useHyperstack } from "hyperstack-react";
import {
ORE_STREAM_STACK,
OreRoundCompletedSchema,
} from "hyperstack-stacks/ore";
function FullyLoadedRounds() {
const { views } = useHyperstack(ORE_STREAM_STACK);
const { data: rounds } = views.OreRound.latest.use({
schema: OreRoundCompletedSchema,
});
return (
<ul>
{rounds?.map((round) => (
<li key={round.id.round_id}>
Round #{round.id.round_id}{round.state.motherlode}
</li>
))}
</ul>
);
}

Define your own Zod schema to validate only the fields your component needs:

import { z } from "zod";
import { useHyperstack } from "hyperstack-react";
import { PUMPFUN_STREAM_STACK } from "hyperstack-stacks/pumpfun";
const TradableTokenSchema = z.object({
id: z.object({ mint: z.string() }),
reserves: z.object({ current_price_sol: z.number() }),
trading: z.object({ total_volume: z.number() }),
});
function TokenList() {
const { views } = useHyperstack(PUMPFUN_STREAM_STACK);
// Only tokens with price and volume data
const { data: tokens } = views.PumpfunToken.list.use({
schema: TradableTokenSchema,
});
return (
<ul>
{tokens?.map((token) => (
<li key={token.id.mint}>
{token.reserves.current_price_sol} SOL — Vol: {token.trading.total_volume}
</li>
))}
</ul>
);
}

See Schema Validation for the full guide on frame validation, generated schemas, and the Schema<T> interface.


Stacks can include data resolved server-side that doesn’t live on-chain. For example, the ORE stack enriches rounds with token metadata (name, symbol, decimals) — no extra API calls needed:

function RoundWithMetadata() {
const { views } = useHyperstack(ORE_STREAM_STACK);
const { data: round } = views.OreRound.latest.useOne();
return (
<div>
<p>Token: {round?.ore_metadata?.name} ({round?.ore_metadata?.symbol})</p>
<p>Decimals: {round?.ore_metadata?.decimals}</p>
</div>
);
}

Resolved data arrives as part of the entity alongside on-chain fields. See Resolvers for details on how resolved data works.


The React SDK re-exports everything from hyperstack-typescript. Access low-level APIs when needed:

// Import core features from React SDK
import { HyperStack, ConnectionManager } from "hyperstack-react";

Use CaseRecommended SDK
React/Next.js appshyperstack-react
Vue, Svelte, Solid, etc.hyperstack-typescript
Node.js backend/scriptshyperstack-typescript
Custom state managementhyperstack-typescript
Need React hookshyperstack-react
Maximum controlhyperstack-typescript

IssueSolution
”WebSocket connection failed”Check your connection to wss://ore.stack.usehyperstack.com
Data not updatingVerify the view path matches the stack entity name (e.g., OreRound/list)
TypeScript errorsEnsure your interface matches the stack’s data shape