Framework Hooks
Idiomatic bindings for React, Vue, Svelte, and Angular — all SSR-safe, all returning the same shape, so swapping frameworks in a multi-stack app doesn't change your data layer. There's a plain framework-agnostic client too.
#React — @absolutejs/sync/react
useSyncCollection returns { data, status, error, mutate }; useCollaborativeText returns { text, setText, status }. The socket opens in an effect, re-opens if the subscription identity changes, and closes on unmount.
import { useSyncCollection, useCollaborativeText } from '@absolutejs/sync/react';
const Issues = () => {
const { data, status, mutate } = useSyncCollection({
url: 'ws://localhost:3000/sync/ws',
collection: 'issues',
});
return (
<ul>{data.map((issue) => <li key={issue.id}>{issue.title}</li>)}</ul>
);
};
const Description = ({ issueId }: { issueId: string }) => {
const doc = useCollaborativeText({
url: 'ws://localhost:3000/sync/ws',
collection: 'issues',
field: 'body',
id: issueId,
});
return (
<textarea value={doc.text} onChange={(e) => doc.setText(e.target.value)} />
);
};#Vue — @absolutejs/sync/vue
Composables returning reactive refs. SSR-safe — the socket opens in onMounted and closes in onUnmounted.
<script setup lang="ts">
import { useSyncCollection, useCollaborativeText } from '@absolutejs/sync/vue';
const { data, status, mutate } = useSyncCollection<Issue>({
url: 'ws://localhost:3000/sync/ws',
collection: 'issues',
});
const { text: docText, setText: setDocText } = useCollaborativeText({
url: 'ws://localhost:3000/sync/ws',
collection: 'issues',
field: 'body',
id: 'shared',
});
</script>
<template>
<ul>
<li v-for="issue in data" :key="issue.id">{{ issue.title }}</li>
</ul>
<textarea
:value="docText"
@input="setDocText(($event.target as HTMLTextAreaElement).value)"
></textarea>
</template>#Svelte — @absolutejs/sync/svelte
Real Svelte stores — $store gives the live state, with mutate/setText/destroy attached. Connection opens lazily on the first browser subscription.
<script lang="ts">
import {
createSyncCollectionStore,
createCollaborativeTextStore,
} from '@absolutejs/sync/svelte';
const issues = createSyncCollectionStore<Issue>({
url: 'ws://localhost:3000/sync/ws',
collection: 'issues',
});
const doc = createCollaborativeTextStore({
url: 'ws://localhost:3000/sync/ws',
collection: 'issues',
field: 'body',
id: 'shared',
});
</script>
<ul>
{#each $issues.data as issue (issue.id)}
<li>{issue.title}</li>
{/each}
</ul>
<textarea
value={$doc.text}
oninput={(e) => doc.setText(e.currentTarget.value)}
></textarea>#Angular — @absolutejs/sync/angular
An injectable service returning Angular signals.connect opens a collection; collaborativeText opens a CRDT field. The service closes everything on its own destroy.
import { Component, inject } from '@angular/core';
import { SyncCollectionService } from '@absolutejs/sync/angular';
@Component({
selector: 'issues-page',
standalone: true,
template: `
@for (issue of issues.data(); track issue.id) {
<li>{{ issue.title }}</li>
}
<textarea
[value]="doc.text()"
(input)="doc.setText(($event.target as HTMLTextAreaElement).value)"
></textarea>
`,
})
export class IssuesPageComponent {
private sync = inject(SyncCollectionService);
issues = this.sync.connect<Issue>({
url: 'ws://localhost:3000/sync/ws',
collection: 'issues',
});
doc = this.sync.collaborativeText({
url: 'ws://localhost:3000/sync/ws',
collection: 'issues',
field: 'body',
id: 'shared',
});
}#Plain client — @absolutejs/sync/client
Every framework binding is a thin wrapper over the same framework-agnostic client. Use it directly when you don't want a framework binding, or to build your own:
// All four framework bindings are thin wrappers over a framework-agnostic
// client. You can use it directly when you don't want a framework binding:
import { createSyncCollection, createCollaborativeText } from '@absolutejs/sync/client';
const issues = createSyncCollection<Issue>({
url: 'ws://localhost:3000/sync/ws',
collection: 'issues',
});
issues.subscribe((state) => render(state.data));
const doc = createCollaborativeText({
url: 'ws://localhost:3000/sync/ws',
collection: 'issues',
field: 'body',
id: 'shared',
});
doc.subscribe(({ text }) => renderTextarea(text));