@rune-hub/react
v1.1.0
Published
Integrating RuneHub with React
Maintainers
Readme
@rune-hub/react provides React integration for the reactive rune-hub store, implementing a flexible subscription mechanism for state changes and mutation calls via specialized hooks.
The HubProvider component injects the store instance into the React context, while the useRune and useAction hooks ensure reactive data synchronization and stable references to action functions, minimizing unnecessary re-renders.
With full TypeScript support and straightforward integration, the library enables predictable state management architecture without boilerplate, while maintaining high performance and compatibility with modern build tools.
Index
[ Install ]
[ Examples ] Basic Counter • Todo List
[ API ] HubProvider • useRune • useSlot • useOn • useAction • useHub • useGetSlot
[ Links ]
Install
🏠︎ / Install ↓
Requires React 18+ and rune-hub 1.0+.
Use with any modern bundler (Vite, Webpack, Rollup, etc.) or framework (Next.js, Remix, etc.).
npm i rune-hub @rune-hub/reactExamples
🏠︎ / Examples ↑ ↓
Basic Counter
🏠︎ / Examples / Basic Counter ↓
A simple counter demonstrating.
import { slot } from 'rune-hub'
import { useRune } from '@rune-hub/react'
const count = () => 0
const increment = () => slot(count).value++
const decrement = () => slot(count).value--
function Counter () {
const value = useRune(count)
return (
<div>
<button onClick={decrement}>-</button>
<span>{value}</span>
<button onClick={increment}>+</button>
</div>
)
}Todo List
🏠︎ / Examples / Todo List ↑
A todo list showcasing rune-hub features.
Store:
import { get, set, update } from 'rune-hub'
export interface Todo {
id: number
text: string
done: boolean
}
let nextId = 1
export const todos = (): Todo[] => []
export const addTodo = (text: string) => {
get(todos).push({ id: nextId++, text, done: false })
update(todos)
}
export const toggleTodo = (todoId: number) => {
set(todos, get(todos).map(todo =>
todoId === todo.id ? { ...todo, done: !todo.done } : todo,
))
}Component:
import { useState } from 'react'
import { useRune } from '@rune-hub/react'
import { todos, addTodo, toggleTodo } from './store'
function TodoList () {
const todoList = useRune(todos)
const [text, setText] = useState('')
const handleSubmit = (e: any) => {
e.preventDefault()
if (text.trim()) {
addTodo(text.trim())
setText('')
}
}
return (
<div>
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={e => setText(e.target.value)}
placeholder='What needs to be done?'
/>
<button type='submit'>Add</button>
</form>
<ul>
{todoList.map(({ id, done, text }) => (
<li
key={id}
onClick={() => toggleTodo(id)}
style={{ textDecoration: done ? 'line-through' : 'none' }}
>
{text}
</li>
))}
</ul>
</div>
)
}API
🏠︎ / API ↑ ↓
HubProvider • useRune • useSlot • useOn • useAction • useHub • useGetSlot
HubProvider
🏠︎ / API / HubProvider ↓
The HubProvider wraps your component tree and makes a Hub instance available to all child components via useHub hook.
import { Hub } from 'rune-hub'
import { HubProvider } from '@rune-hub/react'
const myHub = new Hub()
function App () {
return (
<HubProvider value={myHub}>
<YourComponents />
</HubProvider>
)
}If you don't provide a Hub, the default Hub.root will be used.
useRune
🏠︎ / API / useRune ↑ ↓
Subscribes to a Rune and returns its current value.
Uses useSyncExternalStore for proper synchronization with React's rendering cycle.
Automatically subscribes to the Rune's slot and unsubscribes on unmount.
import { rune, get } from 'rune-hub'
import { useRune } from '@rune-hub/react'
const count = () => 0
const log = () => console.log(get(count))
function Counter () {
const value = useRune(count)
// Subscribe to count changes
useRune(log)
// Activate log effect
return <div>Count: {value}</div>
}useSlot
🏠︎ / API / useSlot ↑ ↓
Subscribes to a Slot and returns its current value.
Uses useSyncExternalStore for proper synchronization with React's rendering cycle.
Automatically subscribes to the Slot and unsubscribes on unmount.
import { Slot } from 'rune-hub'
import { useSlot } from '@rune-hub/react'
const count = new Slot(() => 0)
const log = new Slot(() => console.log(count.value))
function Counter () {
const value = useSlot(count)
// Subscribe to count changes
useSlot(log)
// Activate log effect
return <div>Count: {value}</div>
}useOn
🏠︎ / API / useOn ↑ ↓
Use useOn when you need to run side effects (like logging, analytics, or synchronization) that depend on other Runes, but don't need the return value in your component.
import { rune, get } from 'rune-hub'
import { useRune, useOn } from '@rune-hub/react'
const count = () => 0
const log = () => console.log(get(count))
function Counter () {
const value = useRune(count)
// Subscribe to count changes
useOn(log)
// Activate log effect
return <div>Count: {value}</div>
}useAction
🏠︎ / API / useAction ↑ ↓
The hook automatically binds your action to context Hub.
import { slot } from 'rune-hub'
import { useRune, useAction } from '@rune-hub/react'
const count = () => 0
const increment = () => slot(count).value++
const decrement = () => slot(count).value--
function Counter () {
const value = useRune(count)
const inc = useAction(increment)
const dec = useAction(decrement)
return (
<div>
<button onClick={dec}>-</button>
<span>{value}</span>
<button onClick={inc}>+</button>
</div>
)
}useHub
🏠︎ / API / useHub ↑ ↓
Returns the current Hub instance from context.
import { useHub } from '@rune-hub/react'
function MyComponent () {
const hub = useHub()
console.log(hub)
return <h1>Hello World!</h1>
}useGetSlot
🏠︎ / API / useGetSlot ↑
Returns a Slot instance for a given Rune within the current Hub context.
A Slot is a Hub-scoped reactive container that tracks changes to a Rune. Use this when you need direct access to the Slot API rather than just the value.
import { useEffect } from 'react'
import { useGetSlot } from '@rune-hub/react'
const count = () => 0
function Counter () {
const slot = useGetSlot(count)
useEffect(() => {
// Access raw value without subscribing
console.log(slot.raw)
// Manually listen to changes
return slot.on('change', () => {
console.log('Count changed:', slot.raw)
})
}, [slot])
return <div>Count: {slot.raw}</div>
}The Slot instance is memoized and stable for the Rune and Hub combination.
Links
🏠︎ / Links ↑
- Creator: Mike Lysikov
- Source Code: GitHub
- Repository: npm • npmx
- Utils: @rune-hub/utils
Contributions are welcome! Please feel free to submit issues and pull requests.
