> ## Documentation Index
> Fetch the complete documentation index at: https://velt.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Text Store

> Add real-time collaborative text data to your application using Velt CRDT.

> [!NOTE]
>
> Complete [Steps 1-2](https://velt.dev/docs/realtime-collaboration/crdt/setup/core.md#setup) on the Core setup page before continuing. Those steps install dependencies and initialize Velt.

## Setup

### Step 1: Create a CRDT text store

> [tab] React / Next.js
>
> Use the `useStore` hook to create a CRDT store backed by a Yjs `Y.Text`. The hook handles store initialization, React lifecycle, and real-time subscriptions automatically — no manual `useState`/`useEffect` needed for store setup.
>
> ```tsx theme={null}
> import { useStore } from '@veltdev/crdt-react';
>
> function Component() {
>   const {
>     value: text,
>     update: updateText,
>     store,
>     isLoading,
>     isSynced,
>     status,
>     error,
>   } = useStore<string>({
>     storeId: 'my-text-store',
>     type: 'text',
>     initialValue: 'Hello, start typing here...',
>   });
>
>   if (isLoading) return <div>Loading...</div>;
>   if (error) return <div>Error: {error.message}</div>;
>
>   return (
>     <textarea
>       value={text ?? ''}
>       onChange={(e) => updateText(e.target.value)}
>     />
>   );
> }
> ```

> [tab] Other Frameworks
>
> Use `createVeltStore` to create a CRDT store backed by a Yjs `Y.Text`. The `initialValue` is only applied when the document is brand-new (no prior remote state exists).
>
> ```js theme={null}
> import { createVeltStore } from '@veltdev/crdt';
>
> async function initializeStore(client) {
>   const store = await createVeltStore({
>     id: 'my-text-store',
>     type: 'text',
>     initialValue: 'Hello, start typing here...',
>     veltClient: client,
>   });
>   if (!store) return;
>
>   // Seed the UI with the current CRDT value
>   const text = store.getValue() || '';
>   renderText(text);
> }
> ```

### Step 2: Read the store

Read the current text value at any time. The CRDT library keeps the value automatically synced with all collaborators.

> [tab] React / Next.js
>
> The hook's `value` field reactively tracks the current text — re-renders automatically on every local and remote change, so no manual subscription is needed. You can also read the latest synchronous value at any time via `store.getValue()` (useful inside event handlers).
>
> ```tsx theme={null}
> const { value: text, store } = useStore<string>({
>   storeId: 'my-text-store',
>   type: 'text',
>   initialValue: '',
> });
>
> // Reactive value from the hook — already up to date
> const currentText = text ?? '';
>
> // Synchronous read of the latest value (useful inside event handlers)
> const latest = store.getValue() ?? '';
> ```

> [tab] Other Frameworks
>
> Use `store.getValue()` to read the current synchronous value, and `store.subscribe()` to listen for every change (local and remote) with the full merged text.
>
> ```js theme={null}
> // Read the current text value
> const currentText = store.getValue() || '';
>
> // Subscribe to all future changes (local and remote)
> const unsubscribe = store.subscribe((newText) => {
>   // Re-render the UI with the latest merged state
>   renderText(typeof newText === 'string' ? newText : '');
> });
>
> // Call unsubscribe to stop listening when no longer needed
> unsubscribe();
> ```

### Step 3: Update the store

Use `update()` to replace the entire text value. The CRDT library handles conflict-free merging with other users' edits automatically.

> [tab] React / Next.js
>
> ```tsx theme={null}
> const { update: updateText } = useStore<string>({
>   storeId: 'my-text-store',
>   type: 'text',
>   initialValue: '',
> });
>
> // Replace the entire text content
> function handleChange(newText: string) {
>   updateText(newText);
> }
> ```

> [tab] Other Frameworks
>
> ```js theme={null}
> // Replace the entire text content
> function updateText(newText) {
>   store.update(newText);
> }
> ```

### Step 4: Save and restore versions (optional)

Create checkpoints and roll back when needed.

> [tab] React / Next.js
>
> The `useStore` hook exposes version management methods directly:
>
> ```tsx theme={null}
> import { Version } from '@veltdev/crdt-react';
>
> const {
>   value: text,
>   update: updateText,
>   saveVersion,
>   getVersions,
>   getVersionById,
>   restoreVersion,
>   setStateFromVersion,
> } = useStore<string>({
>   storeId: 'my-text-store',
>   type: 'text',
>   initialValue: '',
> });
>
> // Save a named snapshot of the current state
> await saveVersion('Draft v1');
>
> // Retrieve the list of all saved versions
> const versions: Version[] = await getVersions();
>
> // Restore the store to a previously saved version
> await restoreVersion(versionId);
> const version = await getVersionById(versionId);
> if (version) {
>   await setStateFromVersion(version);
> }
> ```

> [tab] Other Frameworks
>
> ```js theme={null}
> import { Version } from '@veltdev/crdt';
>
> // Save a named snapshot of the current state
> const versionId = await store.saveVersion('Draft v1');
>
> // List all saved versions
> const versions = await store.getVersions();
>
> // Restore the store to a previously saved version
> await store.restoreVersion(versionId);
> const version = await store.getVersionById(versionId);
> if (version) {
>   await store.setStateFromVersion(version);
> }
> ```

### Step 5: Initial content with forceResetInitialContent (optional)

By default, `initialValue` is only applied when the document has no existing remote state. Set `forceResetInitialContent` to `true` to always reset the store to `initialValue` on initialization, overwriting any existing remote data.

> [tab] React / Next.js
>
> ```tsx theme={null}
> const { value: text, update: updateText } = useStore<string>({
>   storeId: 'my-text-store',
>   type: 'text',
>   initialValue: defaultText,
>   forceResetInitialContent: true,
> });
> ```

> [tab] Other Frameworks
>
> ```js theme={null}
> const store = await createVeltStore({
>   id: 'my-text-store',
>   type: 'text',
>   initialValue: defaultText,
>   veltClient: client,
>   forceResetInitialContent: true,
> });
> ```

## Complete Example

> [tab] React / Next.js
>
> A complete collaborative notepad built with `useStore`:
>
> ```tsx Complete Implementation expandable lines theme={null}
> import React, { useState, useEffect, useCallback } from 'react';
> import { useStore, Version } from '@veltdev/crdt-react';
>
> export const Notepad = () => {
>   const [versionName, setVersionName] = useState('');
>   const [versions, setVersions] = useState<Version[]>([]);
>
>   // Use the useStore hook — handles initialization and subscriptions automatically
>   const {
>     value: text,
>     update: updateText,
>     store,
>     saveVersion: storeSaveVersion,
>     getVersions: storeGetVersions,
>     restoreVersion: storeRestoreVersion,
>     getVersionById,
>     setStateFromVersion,
>   } = useStore<string>({
>     storeId: 'my-notepad-store',
>     type: 'text',
>     initialValue: 'Welcome to the Collaborative Notepad! Start typing here...',
>   });
>
>   // Fetch saved versions when store is ready
>   const refreshVersions = useCallback(async () => {
>     const v = await storeGetVersions();
>     setVersions(v);
>   }, [storeGetVersions]);
>
>   useEffect(() => {
>     if (store) refreshVersions();
>   }, [refreshVersions, store]);
>
>   // Handle text changes from the textarea
>   const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
>     updateText(e.target.value);
>   };
>
>   // Handle saving a new version
>   const handleSaveVersion = async (e: React.FormEvent) => {
>     e.preventDefault();
>     if (versionName.trim()) {
>       await storeSaveVersion(versionName.trim());
>       setVersionName('');
>       await refreshVersions();
>     }
>   };
>
>   // Handle restoring a version
>   const handleRestoreVersion = async (versionId: string) => {
>     await storeRestoreVersion(versionId);
>     const version = await getVersionById(versionId);
>     if (version) {
>       await setStateFromVersion(version);
>     }
>     await refreshVersions();
>   };
>
>   return (
>     <div>
>       <textarea
>         value={text ?? ''}
>         onChange={handleTextChange}
>         placeholder="Start typing..."
>       />
>       <div>{(text ?? '').length} character{(text ?? '').length !== 1 ? 's' : ''}</div>
>
>       <h3>Versions</h3>
>       <form onSubmit={handleSaveVersion}>
>         <input
>           type="text"
>           value={versionName}
>           onChange={(e) => setVersionName(e.target.value)}
>           placeholder="Version name..."
>         />
>         <button type="submit">Save Version</button>
>       </form>
>
>       <ul>
>         {versions.map((version) => (
>           <li key={version.versionId}>
>             <span>{version.versionName}</span>
>             <button onClick={() => handleRestoreVersion(version.versionId)}>Restore</button>
>           </li>
>         ))}
>       </ul>
>     </div>
>   );
> };
> ```

> [tab] Other Frameworks
>
> A complete collaborative notepad with SDK initialization, store management, and version control.
>
> **velt.ts**
>
> ```ts Complete velt.ts expandable lines theme={null}
> import { initVelt } from '@veltdev/client';
> import type { Velt } from '@veltdev/types';
>
> let client: Velt | null = null;
> let veltInitialized = false;
>
> // Subscriber registry for SDK-ready notifications
> const veltInitSubscribers = new Map<string, (velt: Velt) => void>();
>
> async function initializeVelt() {
>   // Initialize the Velt SDK
>   client = await initVelt('YOUR_API_KEY');
>
>   // Scope all collaboration to the configured document
>   client.setDocument('crdt-text-demo-doc-1', { documentName: 'CRDT Text Demo' });
>
>   // Track user login/logout
>   client.getCurrentUser().subscribe((currentUser) => {
>     renderUserControls(currentUser);
>   });
>
>   // Track SDK-ready state and notify subscribers
>   client.getVeltInitState().subscribe((isReady) => {
>     veltInitialized = isReady;
>     if (isReady && client) {
>       veltInitSubscribers.forEach((callback) => callback(client));
>     }
>   });
> }
>
> // Subscribe to the SDK being fully initialized and ready
> export const subscribeToVeltInit = (subscriberId: string, callback: (velt: Velt) => void) => {
>   veltInitSubscribers.set(subscriberId, callback);
>   if (veltInitialized && client) {
>     callback(client);
>   }
> };
>
> // Authenticate a user with the Velt backend
> export async function loginWithUser(userId: string) {
>   await client?.identify({ userId, name: userId });
> }
>
> // Sign the current user out
> export async function logout() {
>   await client?.signOutUser();
> }
>
> // Start SDK initialization on module load
> initializeVelt();
> ```
>
> **notepad.ts**
>
> ```ts Complete notepad.ts expandable lines theme={null}
> import { Store, Version, createVeltStore } from '@veltdev/crdt';
> import type { Velt } from '@veltdev/types';
> import { subscribeToVeltInit } from './velt';
>
> let store: Store<string> | null = null;
> let text = '';
> let versions: Version[] = [];
>
> // Initialize the store when the SDK is ready
> async function initStore(veltClient: Velt) {
>   // Create the CRDT text store with initial seed content
>   const textStore = await createVeltStore<string>({
>     id: 'my-notepad-store',
>     type: 'text',
>     initialValue: 'Welcome to the Collaborative Notepad! Start typing here...',
>     veltClient: veltClient,
>   });
>   if (!textStore) return;
>   store = textStore;
>
>   // Seed UI with the current CRDT value
>   text = textStore.getValue() || '';
>   renderNotepad();
>
>   // Subscribe to all future changes (local and remote)
>   textStore.subscribe((newText) => {
>     text = typeof newText === 'string' ? newText : '';
>     renderNotepad();
>   });
>
>   // Load saved versions
>   await refreshVersions();
> }
>
> // Wait for the SDK to be ready, then initialize the store
> subscribeToVeltInit('notepad', (velt) => {
>   initStore(velt);
> });
>
> // Replace the entire text content
> function updateText(newText: string) {
>   if (!store) return;
>   store.update(newText);
> }
>
> // Save a named snapshot of the current state
> async function saveVersionHandler(name: string) {
>   if (!store) return;
>   await store.saveVersion(name);
>   await refreshVersions();
> }
>
> // Restore the store to a previously saved version
> async function restoreVersionHandler(versionId: string) {
>   if (!store) return;
>   await store.restoreVersion(versionId);
>   const version = await store.getVersionById(versionId);
>   if (version) {
>     await store.setStateFromVersion(version);
>   }
>   await refreshVersions();
> }
>
> // Fetch the latest version list from the backend
> async function refreshVersions() {
>   if (!store) return;
>   versions = await store.getVersions();
>   renderVersions();
> }
>
> // Render the notepad textarea and character count into the DOM
> function renderNotepad() {
>   const textarea = document.querySelector('.notepad-textarea') as HTMLTextAreaElement;
>   if (textarea && textarea !== document.activeElement) {
>     textarea.value = text;
>   }
>
>   const charCount = document.querySelector('.char-count');
>   if (charCount) {
>     charCount.textContent = `${text.length} character${text.length !== 1 ? 's' : ''}`;
>   }
> }
>
> // Render the version list into the DOM
> function renderVersions() {
>   const versionList = document.querySelector('.version-list');
>   if (!versionList) return;
>
>   versionList.innerHTML = '';
>   for (const version of versions) {
>     const li = document.createElement('li');
>
>     const nameSpan = document.createElement('span');
>     nameSpan.textContent = version.versionName;
>
>     const restoreBtn = document.createElement('button');
>     restoreBtn.textContent = 'Restore';
>     restoreBtn.addEventListener('click', () => restoreVersionHandler(version.versionId));
>
>     li.appendChild(nameSpan);
>     li.appendChild(restoreBtn);
>     versionList.appendChild(li);
>   }
> }
>
> // Attach form and textarea event handlers to the DOM
> export function setupNotepadForm() {
>   // Handle textarea input events
>   const textarea = document.querySelector('.notepad-textarea') as HTMLTextAreaElement;
>   if (textarea) {
>     textarea.addEventListener('input', () => {
>       text = textarea.value;
>       updateText(text);
>
>       const charCount = document.querySelector('.char-count');
>       if (charCount) {
>         charCount.textContent = `${text.length} character${text.length !== 1 ? 's' : ''}`;
>       }
>     });
>   }
>
>   // Handle version form submission
>   const versionForm = document.getElementById('version-form');
>   if (versionForm) {
>     versionForm.addEventListener('submit', (event) => {
>       event.preventDefault();
>       const input = versionForm.querySelector('.version-input') as HTMLInputElement;
>       if (input && input.value.trim()) {
>         saveVersionHandler(input.value.trim());
>         input.value = '';
>       }
>     });
>   }
> }
> ```
>
> **main.ts**
>
> ```ts Complete main.ts expandable lines theme={null}
> import './style.css';
> import './velt';
> import { setupNotepadForm } from './notepad';
>
> // Attach form handlers once the DOM is ready
> setupNotepadForm();
> ```
>
> **HTML structure**
>
> ```html Complete index.html expandable lines theme={null}
> <div class="app-container">
>   <header class="app-header">
>     <h1>CRDT Text Demo</h1>
>     <div id="user-controls"></div>
>   </header>
>
>   <main class="app-content">
>     <div id="notepad-header">Collaborative Notepad - Please login to start editing</div>
>
>     <textarea class="notepad-textarea" placeholder="Start typing..."></textarea>
>     <div class="char-count">0 characters</div>
>
>     <div class="versions-section">
>       <h3>Versions</h3>
>       <form id="version-form">
>         <input type="text" class="version-input" placeholder="Version name..." />
>         <button type="submit">Save Version</button>
>       </form>
>       <ul class="version-list"></ul>
>     </div>
>   </main>
> </div>
> ```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.