Skip to content

Vue integration

The SDK is framework-agnostic. For Vue 3, wrap it in a composable that manages connection lifecycle and exposes reactive state.

import { ref, shallowRef, onScopeDispose } from 'vue'
import { createClient, type SportEvent, type SportEventId } from 'realtimeodds'
export function useRealtimeOdds(apiKey: string) {
const events = shallowRef<ReadonlyMap<SportEventId, SportEvent>>(new Map())
const connected = ref(false)
const client = createClient({
url: 'wss://api.realtimeodds.xyz',
apiKey
})
const refresh = () => {
events.value = new Map(client.snapshot().sportEvents)
}
client.on('connected', () => { connected.value = true; refresh() })
client.on('disconnected', () => { connected.value = false })
client.on('sportEvent:added', refresh)
client.on('sportEvent:updated', refresh)
client.on('sportEvent:removed', refresh)
client.connect().catch(err => console.error('connect failed:', err))
onScopeDispose(() => { void client.disconnect() })
return { events, connected }
}

Used in a component:

<script setup lang="ts">
import { useRealtimeOdds } from './useRealtimeOdds'
const { events, connected } = useRealtimeOdds(import.meta.env.VITE_REALTIMEODDS_API_KEY)
</script>
<template>
<p v-if="!connected">Connecting…</p>
<ul v-else>
<li v-for="ev of events.values()" :key="ev.id">
[{{ ev.bookmaker }}] {{ ev.name }} — {{ ev.markets.size }} markets
</li>
</ul>
</template>

Use shallowRef for the events map. ref would deep-track every nested entity Vue’s reactivity system encounters, which is expensive for thousands of objects you never mutate. shallowRef only tracks reassignments — exactly what you want when each update produces a fresh Map.

For more granular reactivity (per-row updates), see the per-event pattern below.

For dashboards rendering hundreds of rows, prefer per-row subscriptions to avoid re-rendering the whole list on every odds tick:

import { ref, watch, onScopeDispose } from 'vue'
import type { Client, SelectionId } from 'realtimeodds'
export function useSelectionPrice(client: Client, selectionId: SelectionId) {
const price = ref<number | undefined>()
const handler = (payload: { selectionId: SelectionId; quote: { price: number } }) => {
if (payload.selectionId === selectionId) {
price.value = payload.quote.price
}
}
client.on('odds:changed', handler)
onScopeDispose(() => client.off('odds:changed', handler))
return { price }
}

Provide a single client at the app root and inject it where needed:

main.ts
import { createApp } from 'vue'
import { createClient } from 'realtimeodds'
const client = createClient({
url: 'wss://api.realtimeodds.xyz',
apiKey: import.meta.env.VITE_REALTIMEODDS_API_KEY
})
const app = createApp(App)
app.provide('realtimeOdds', client)
client.connect().then(() => app.mount('#app')).catch(console.error)

Components read it via inject:

import { inject } from 'vue'
import type { Client } from 'realtimeodds'
const client = inject<Client>('realtimeOdds')!

If you use Pinia, the same pattern applies — initialize the client in an action, store the snapshot in state via shallowRef / markRaw, and update it on events. Avoid letting Pinia deep-track entity instances; mark them raw or pass them through shallowRef.