useRealtimeState
useRealtimeState
Share reactive state across all connected clients. Works just like Vue's useState but automatically synchronizes changes in real-time.
Usage
<script setup>
const counter = useRealtimeState('counter', 0)
</script>
<template>
<button @click="counter++">
Count: {{ counter }}
</button>
</template>
Type Signature
function useRealtimeState<T>(
key: string,
defaultValue?: T,
options?: UseRealtimeStateOptions
): UseRealtimeStateReturn<T>
Parameters
key
- Type:
string - Required: Yes
A unique identifier for the state. All clients using the same key will share the same state.
// Different keys = different state
const counter = useRealtimeState('counter', 0)
const users = useRealtimeState('online-users', [])
const settings = useRealtimeState('app-settings', { theme: 'dark' })
defaultValue
- Type:
T - Required: No
The initial value to use if no state exists on the server. If the server already has a value for this key, that value will be used instead.
// Primitives
const count = useRealtimeState('count', 0)
const name = useRealtimeState('name', 'Anonymous')
const enabled = useRealtimeState('enabled', true)
// Objects
const profile = useRealtimeState('profile', {
name: 'User',
avatar: '/default.png',
})
// Arrays
const messages = useRealtimeState('messages', [])
options
- Type:
UseRealtimeStateOptions - Required: No
interface UseRealtimeStateOptions {
/**
* Whether to optimistically update the value on update
* @default true
*/
optimisticUpdates?: boolean
/**
* The timeout for value updates in milliseconds
* @default 5000
*/
updateTimeout?: number
}
Return Value
Returns a WritableComputedRef<T> with additional properties:
interface UseRealtimeStateReturn<T> extends WritableComputedRef<T> {
/**
* Loading state - true while fetching initial value
*/
loading: Readonly<Ref<boolean>>
/**
* Manually refresh the value from the server
*/
refresh: () => void
}
value
Access and update the state value:
const counter = useRealtimeState('counter', 0)
// Read
console.log(counter.value) // 0
// Write (syncs to all clients)
counter.value = 10
counter.value++
loading
A readonly ref indicating whether the initial value is being fetched:
<script setup>
const data = useRealtimeState('data', null)
</script>
<template>
<div v-if="data.loading.value">Loading...</div>
<div v-else>{{ data }}</div>
</template>
refresh()
Manually fetch the latest value from the server:
const counter = useRealtimeState('counter', 0)
// Force refresh from server
counter.refresh()
Examples
Counter
A simple counter that syncs across all clients:
<script setup>
const counter = useRealtimeState('counter', 0)
</script>
<template>
<div class="flex items-center gap-4">
<button @click="counter--">-</button>
<span>{{ counter }}</span>
<button @click="counter++">+</button>
</div>
</template>
Shared Form
A form where all fields sync in real-time:
<script setup>
const form = useRealtimeState('contact-form', {
name: '',
email: '',
message: '',
})
</script>
<template>
<form>
<input v-model="form.name" placeholder="Name" />
<input v-model="form.email" type="email" placeholder="Email" />
<textarea v-model="form.message" placeholder="Message" />
</form>
</template>
Online Users List
Track online users across sessions:
<script setup>
const onlineUsers = useRealtimeState('online-users', [])
const currentUser = { id: crypto.randomUUID(), name: 'User' }
onMounted(() => {
onlineUsers.value = [...onlineUsers.value, currentUser]
})
onUnmounted(() => {
onlineUsers.value = onlineUsers.value.filter(u => u.id !== currentUser.id)
})
</script>
<template>
<ul>
<li v-for="user in onlineUsers" :key="user.id">
{{ user.name }}
</li>
</ul>
</template>
With Loading State
Show a loading indicator while fetching initial state:
<script setup>
const data = useRealtimeState('large-dataset', [], {
updateTimeout: 10000, // Longer timeout for large data
})
</script>
<template>
<div>
<div v-if="data.loading.value" class="animate-pulse">
Loading data...
</div>
<div v-else>
<p>{{ data.length }} items loaded</p>
<!-- render data -->
</div>
</div>
</template>
Disable Optimistic Updates
For critical data where you want to wait for server confirmation:
<script setup>
const balance = useRealtimeState('account-balance', 0, {
optimisticUpdates: false, // Wait for server confirmation
})
async function withdraw(amount) {
// Value won't update until server confirms
balance.value = balance.value - amount
}
</script>
How It Works
- Initial Load: When the composable is created, it fetches the current value from the server
- Subscription: The client subscribes to updates for the specified key
- Updates: When you modify the value:
- If
optimisticUpdatesis true (default), the UI updates immediately - The change is sent to the server
- The server broadcasts the update to all other subscribed clients
- If the server rejects the update, the value is rolled back
- If
- Cleanup: When the component unmounts, the subscription is automatically removed