> ## 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.

# Array Store

> Add real-time collaborative lists and collections 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 array store

> [tab] React / Next.js
>
> Use the `useStore` hook to create a CRDT store backed by a Yjs `Y.Array`. 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';
>
> interface Item {
>   id: string;
>   name: string;
> }
>
> function Component() {
>   const {
>     value: items,
>     update: updateItems,
>     store,
>     isLoading,
>     isSynced,
>     status,
>     error,
>   } = useStore<Item[]>({
>     storeId: 'my-array-store',
>     type: 'array',
>     initialValue: [{ id: '1', name: 'First item' }],
>   });
>
>   if (isLoading) return <div>Loading...</div>;
>   if (error) return <div>Error: {error.message}</div>;
>
>   const itemList = Array.isArray(items) ? items : [];
>
>   return (
>     <ul>
>       {itemList.map((item) => (
>         <li key={item.id}>{item.name}</li>
>       ))}
>     </ul>
>   );
> }
> ```

> [tab] Other Frameworks
>
> Use `createVeltStore` to create a CRDT store backed by a Yjs `Y.Array`. 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-array-store',
>     type: 'array',
>     initialValue: [{ id: '1', name: 'First item' }],
>     veltClient: client,
>   });
>   if (!store) return;
>
>   // Seed the UI with the current CRDT value
>   const items = store.getValue() || [];
>   renderItems(items);
> }
> ```

### Step 2: Read the store

Read the current array 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 array — 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: items, store } = useStore<Item[]>({
>   storeId: 'my-array-store',
>   type: 'array',
>   initialValue: [],
> });
>
> // Reactive value from the hook — already up to date
> const itemList = Array.isArray(items) ? items : [];
>
> // Synchronous read of the latest value (useful inside event handlers)
> const current = 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 array.
>
> ```js theme={null}
> // Read the current array value
> const currentItems = store.getValue() || [];
>
> // Subscribe to all future changes (local and remote)
> const unsubscribe = store.subscribe((newItems) => {
>   // Re-render the UI with the latest merged state
>   renderItems(Array.isArray(newItems) ? newItems : []);
> });
>
> // Call unsubscribe to stop listening when no longer needed
> unsubscribe();
> ```

### Step 3: Update the store

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

> [tab] React / Next.js
>
> ```tsx theme={null}
> // Add a new item to the end of the array
> const addItem = (name: string) => {
>   const current = store.getValue() || [];
>   if (Array.isArray(current)) {
>     updateItems([...current, { id: generateId(), name }]);
>   }
> };
>
> // Update an item by its ID
> const updateItem = (itemId: string, newName: string) => {
>   const current = store.getValue() || [];
>   if (Array.isArray(current)) {
>     updateItems(
>       current.map((item) => (item.id === itemId ? { ...item, name: newName } : item))
>     );
>   }
> };
>
> // Remove an item from the array by its ID
> const removeItem = (itemId: string) => {
>   const current = store.getValue() || [];
>   if (Array.isArray(current)) {
>     updateItems(current.filter((item) => item.id !== itemId));
>   }
> };
> ```

> [tab] Other Frameworks
>
> ```js theme={null}
> // Add a new item to the end of the array
> function addItem(name) {
>   const current = store.getValue() || [];
>   store.update([...current, { id: generateId(), name }]);
> }
>
> // Update an item by its ID
> function updateItem(itemId, newName) {
>   const current = store.getValue() || [];
>   store.update(
>     current.map((item) => (item.id === itemId ? { ...item, name: newName } : item))
>   );
> }
>
> // Remove an item from the array by its ID
> function removeItem(itemId) {
>   const current = store.getValue() || [];
>   store.update(current.filter((item) => item.id !== itemId));
> }
> ```

### 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: items,
>   update: updateItems,
>   saveVersion,
>   getVersions,
>   getVersionById,
>   restoreVersion,
>   setStateFromVersion,
> } = useStore<Item[]>({
>   storeId: 'my-array-store',
>   type: 'array',
>   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: items, update: updateItems } = useStore<Item[]>({
>   storeId: 'my-array-store',
>   type: 'array',
>   initialValue: defaultItems,
>   forceResetInitialContent: true,
> });
> ```

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

## Complete Example

> [tab] React / Next.js
>
> A complete collaborative todo list built with `useStore`:
>
> ```tsx Complete Implementation expandable lines theme={null}
> import React, { useState, useEffect, useCallback } from 'react';
> import { useStore, Version } from '@veltdev/crdt-react';
>
> interface Todo {
>   id: string;
>   text: string;
>   completed: boolean;
> }
>
> const generateId = () => Math.random().toString(36).substring(2, 15);
>
> const defaultTodos: Todo[] = [
>   { id: 'seed-1', text: 'Welcome Todo - Edit me!', completed: false },
> ];
>
> export const Todos = () => {
>   const [newTodo, setNewTodo] = useState('');
>   const [versionName, setVersionName] = useState('');
>   const [versions, setVersions] = useState<Version[]>([]);
>
>   // Use the useStore hook — handles initialization and subscriptions automatically
>   const {
>     value: todos,
>     update: updateTodos,
>     store,
>     saveVersion: storeSaveVersion,
>     getVersions: storeGetVersions,
>     restoreVersion: storeRestoreVersion,
>     getVersionById,
>     setStateFromVersion,
>   } = useStore<Todo[]>({
>     storeId: 'my-todo-store',
>     type: 'array',
>     initialValue: defaultTodos,
>   });
>
>   // Fetch saved versions when store is ready
>   const refreshVersions = useCallback(async () => {
>     const v = await storeGetVersions();
>     setVersions(v);
>   }, [storeGetVersions]);
>
>   useEffect(() => {
>     if (store) refreshVersions();
>   }, [refreshVersions, store]);
>
>   const todoList = Array.isArray(todos) ? todos : [];
>
>   // Add a new todo
>   const handleSubmit = (e: React.FormEvent) => {
>     e.preventDefault();
>     if (newTodo.trim() && store) {
>       const newItem: Todo = { id: generateId(), text: newTodo.trim(), completed: false };
>       const current = store.getValue() || [];
>       if (Array.isArray(current)) {
>         updateTodos([...current, newItem]);
>       }
>       setNewTodo('');
>     }
>   };
>
>   // Toggle a todo's completed state
>   const toggleTodo = (todoId: string) => {
>     if (!store) return;
>     const current = store.getValue();
>     if (Array.isArray(current)) {
>       updateTodos(current.map((t: Todo) => t.id === todoId ? { ...t, completed: !t.completed } : t));
>     }
>   };
>
>   // Delete a todo
>   const deleteTodo = (todoId: string) => {
>     if (!store) return;
>     const current = store.getValue();
>     if (Array.isArray(current)) {
>       updateTodos(current.filter((t: Todo) => t.id !== todoId));
>     }
>   };
>
>   // 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>
>       <form onSubmit={handleSubmit}>
>         <input
>           type="text"
>           value={newTodo}
>           onChange={(e) => setNewTodo(e.target.value)}
>           placeholder="Add a new todo..."
>         />
>         <button type="submit">Add</button>
>       </form>
>
>       <ul>
>         {todoList.map((todo) => (
>           <li key={todo.id}>
>             <input
>               type="checkbox"
>               checked={todo.completed}
>               onChange={() => toggleTodo(todo.id)}
>             />
>             <span>{todo.text}</span>
>             <button onClick={() => deleteTodo(todo.id)}>Delete</button>
>           </li>
>         ))}
>       </ul>
>
>       <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 todo list 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-array-demo-doc-1', { documentName: 'CRDT Array 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();
> ```
>
> **todos.ts**
>
> ```ts Complete todos.ts expandable lines theme={null}
> import { Store, Version, createVeltStore } from '@veltdev/crdt';
> import type { Velt } from '@veltdev/types';
> import { subscribeToVeltInit } from './velt';
>
> interface Todo {
>   id: string;
>   text: string;
>   completed: boolean;
> }
>
> let store: Store<Todo[]> | null = null;
> let todos: Todo[] = [];
> let versions: Version[] = [];
>
> // Initialize the store when the SDK is ready
> async function initStore(veltClient: Velt) {
>   // Create the CRDT array store with initial seed data
>   const todoStore = await createVeltStore<Todo[]>({
>     id: 'my-todo-store',
>     type: 'array',
>     initialValue: [{ id: 'seed-1', text: 'Welcome Todo - Edit me!', completed: false }],
>     veltClient: veltClient,
>   });
>   if (!todoStore) return;
>   store = todoStore;
>
>   // Seed UI with the current CRDT value
>   todos = todoStore.getValue() || [];
>   renderTodos();
>
>   // Subscribe to all future changes (local and remote)
>   todoStore.subscribe((newTodos) => {
>     todos = Array.isArray(newTodos) ? newTodos : [];
>     renderTodos();
>   });
>
>   // Load saved versions
>   await refreshVersions();
> }
>
> // Wait for the SDK to be ready, then initialize the store
> subscribeToVeltInit('todos', (velt) => {
>   initStore(velt);
> });
>
> // Add a new todo to the end of the list
> function addTodo(text: string) {
>   if (!store) return;
>   const current = store.getValue() || [];
>   const newTodo: Todo = { id: Math.random().toString(36).substring(2, 15), text, completed: false };
>   store.update([...current, newTodo]);
> }
>
> // Toggle the completed state of a todo by its ID
> function toggleTodo(todoId: string) {
>   if (!store) return;
>   const current = store.getValue() || [];
>   store.update(
>     current.map((todo) => (todo.id === todoId ? { ...todo, completed: !todo.completed } : todo))
>   );
> }
>
> // Remove a todo from the list by its ID
> function deleteTodo(todoId: string) {
>   if (!store) return;
>   const current = store.getValue() || [];
>   store.update(current.filter((todo) => todo.id !== todoId));
> }
>
> // 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 todo list into the DOM
> function renderTodos() {
>   const todoList = document.querySelector('.todo-list');
>   if (!todoList) return;
>
>   todoList.innerHTML = '';
>   for (const todo of todos) {
>     const li = document.createElement('li');
>     li.className = `todo-item${todo.completed ? ' completed' : ''}`;
>
>     const checkbox = document.createElement('input');
>     checkbox.type = 'checkbox';
>     checkbox.checked = todo.completed;
>     checkbox.addEventListener('change', () => toggleTodo(todo.id));
>
>     const span = document.createElement('span');
>     span.textContent = todo.text;
>
>     const deleteBtn = document.createElement('button');
>     deleteBtn.textContent = 'Delete';
>     deleteBtn.addEventListener('click', () => deleteTodo(todo.id));
>
>     li.appendChild(checkbox);
>     li.appendChild(span);
>     li.appendChild(deleteBtn);
>     todoList.appendChild(li);
>   }
> }
>
> // 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 submit handlers to the DOM
> export function setupTodoForm() {
>   const todoForm = document.getElementById('todo-form');
>   if (todoForm) {
>     todoForm.addEventListener('submit', (event) => {
>       event.preventDefault();
>       const input = todoForm.querySelector('.todo-input') as HTMLInputElement;
>       if (input && input.value.trim()) {
>         addTodo(input.value.trim());
>         input.value = '';
>       }
>     });
>   }
>
>   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 { setupTodoForm } from './todos';
>
> // Attach form handlers once the DOM is ready
> setupTodoForm();
> ```
>
> **HTML structure**
>
> ```html Complete index.html expandable lines theme={null}
> <div class="app-container">
>   <header class="app-header">
>     <h1>CRDT Array Demo</h1>
>     <div id="user-controls"></div>
>   </header>
>
>   <main class="app-content">
>     <div id="todos-header">Collaborative Todos - Please login to start editing</div>
>
>     <form id="todo-form">
>       <input type="text" class="todo-input" placeholder="Add a new todo..." />
>       <button type="submit">Add</button>
>     </form>
>
>     <ul class="todo-list"></ul>
>
>     <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.