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

<Info>Complete [Steps 1-2](/realtime-collaboration/crdt/setup/core#setup) on the Core setup page before continuing. Those steps install dependencies and initialize Velt.</Info>

## Setup

### Step 1: Create a CRDT array store

<Tabs>
  <Tab title="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>

  <Tab title="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);
    }
    ```
  </Tab>
</Tabs>

### Step 2: Read the store

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

<Tabs>
  <Tab title="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>

  <Tab title="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();
    ```
  </Tab>
</Tabs>

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

<Tabs>
  <Tab title="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>

  <Tab title="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));
    }
    ```
  </Tab>
</Tabs>

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

Create checkpoints and roll back when needed.

<Tabs>
  <Tab title="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>

  <Tab title="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);
    }
    ```
  </Tab>
</Tabs>

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

<Tabs>
  <Tab title="React / Next.js">
    ```tsx theme={null}
    const { value: items, update: updateItems } = useStore<Item[]>({
      storeId: 'my-array-store',
      type: 'array',
      initialValue: defaultItems,
      forceResetInitialContent: true,
    });
    ```
  </Tab>

  <Tab title="Other Frameworks">
    ```js theme={null}
    const store = await createVeltStore({
      id: 'my-array-store',
      type: 'array',
      initialValue: defaultItems,
      veltClient: client,
      forceResetInitialContent: true,
    });
    ```
  </Tab>
</Tabs>

## Complete Example

<Tabs>
  <Tab title="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>

  <Tab title="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>
    ```
  </Tab>
</Tabs>
