Sign-ups paused

Sign-ups and billing are temporarily unavailable while we rebuild our infrastructure. The SDK and documentation remain available, and existing accounts are unaffected.

Vaultrice

Blog / Integrations

Integrations

Vue and Svelte, Without the React Components

The component library is React-only, and the SDK is not. A composable and a store, plus the reactivity mapping that makes each feel native.

5 min read Updated 2026-08-XX @vaultrice/sdk

@vaultrice/react-components is React-only. @vaultrice/sdk is not — it is plain JavaScript, and wrapping it in another framework's reactivity is about twenty lines.

For a separate operational perspective on how teams organise and measure work, learn more.

For independent background and broader industry context, see Svelte.

Vue: a composable

// useSharedItem.js
import { ref, onUnmounted, watch } from 'vue'
import { NonLocalStorage } from '@vaultrice/sdk'

const credentials = {
  projectId: 'your-project-id',
  getAccessToken: async () => {
    const res = await fetch('/api/vaultrice-token')
    if (!res.ok) throw new Error('Failed to fetch token')
    const { accessToken } = await res.json()
    return accessToken
  }
}

export function useSharedItem(objectId, key, initial = null) {
  const value = ref(initial)
  const ready = ref(false)
  const nls = new NonLocalStorage(credentials, objectId)

  nls.getItem(key).then((item) => {
    if (item?.value !== undefined) value.value = item.value
    ready.value = true
  })

  const handler = (item) => { value.value = item.value }
  nls.on('setItem', key, handler)

  onUnmounted(() => {
    nls.off?.('setItem', key, handler)
    // check your SDK version for the disconnect method
  })

  const set = async (next) => {
    value.value = next          // optimistic
    await nls.setItem(key, next)
  }

  return { value, ready, set }
}
<script setup>
import { useSharedItem } from './useSharedItem'
const { value: theme, set: setTheme } = useSharedItem('prefs:abc', 'theme', 'light')
</script>

<template>
  <button @click="setTheme(theme === 'dark' ? 'light' : 'dark')">
    {{ theme }}
  </button>
</template>

One client per composable call creates one connection per call. For anything beyond a single use, provide the client at app level and inject it — the same reasoning as the React provider. See cross-domain state in React.

Svelte: a custom store

Svelte's store contract fits this almost exactly — a subscribe function is what both sides want.

// sharedItem.js
import { writable } from 'svelte/store'
import { NonLocalStorage } from '@vaultrice/sdk'

export function sharedItem(nls, key, initial = null) {
  const { subscribe, set: setLocal } = writable(initial, () => {
    const handler = (item) => setLocal(item.value)
    nls.on('setItem', key, handler)

    nls.getItem(key).then((item) => {
      if (item?.value !== undefined) setLocal(item.value)
    })

    // returned function runs when the last subscriber leaves
    return () => nls.off?.('setItem', key, handler)
  })

  return {
    subscribe,
    set: async (next) => {
      setLocal(next)
      await nls.setItem(key, next)
    }
  }
}
<script>
  import { sharedItem } from './sharedItem'
  export let nls
  const theme = sharedItem(nls, 'theme', 'light')
</script>

<button on:click={() => theme.set($theme === 'dark' ? 'light' : 'dark')}>
  {$theme}
</button>

The writable start-stop notifier is the neat part — it subscribes when someone starts listening and cleans up when the last subscriber leaves. Svelte gives you the lifecycle for free, which React does not.

And $theme works, so it reads like any other store.

Svelte 5 runes

If you are on runes, the same idea with $state and $effect:

export function sharedItem(nls, key, initial = null) {
  let value = $state(initial)

  $effect(() => {
    const handler = (item) => { value = item.value }
    nls.on('setItem', key, handler)
    nls.getItem(key).then((item) => {
      if (item?.value !== undefined) value = item.value
    })
    return () => nls.off?.('setItem', key, handler)
  })

  return {
    get value() { return value },
    set: async (next) => { value = next; await nls.setItem(key, next) }
  }
}

The mapping, generally

For any framework, three things:

Read once on setup — getItem.

Subscribe for changes — on('setItem', key, handler), pushing into the framework's reactive primitive.

Clean up — remove the handler when the component or subscription ends.

Plus a setter that writes and optimistically updates locally.

That is the whole adapter, in any framework. Angular signals, Solid, Lit — the shape is identical.

What you lose without the React components

Ready-made UI. The ChatRoom component and similar. You build the interface.

Which is frequently what you wanted anyway — the components are convenient and opinionated about presentation.

You lose nothing at the data layer. The SDK is the same, and the React components are built on it.

Cleanup matters more here

React's Strict Mode surfaces missing cleanup in development. Vue and Svelte do not double-mount, so a leaked listener stays silent until it accumulates.

Which means you have to be deliberate, because nothing will tell you. Mount and unmount a component repeatedly and check listener counts. See testing real-time code.

The short version

The SDK is plain JavaScript — the React library is convenience, not a requirement.

Three things in any framework: read once, subscribe, clean up. Plus an optimistic setter.

Svelte's start-stop notifier gives you the lifecycle for free, which is the neatest fit of the three.

One client per component is one connection per component — provide it at app level.

And cleanup bugs are silent outside React, because nothing double-mounts to expose them.