Skip to content

Building Collaborative Apps

Learn how to use Collaborative Flows to sync state across multiple tabs, devices, and users in real time using the new Mesh network features in v3.0.0.

Prerequisites

  • AsyncFlowState v3.0.0
  • A WebSocket or BroadcastChannel understanding

Step 1: Setting up the Mesh

To enable cross-tab communication, we initialize FlowMesh and attach it to our application. This establishes a leader-election process among the connected clients.

typescript
import { FlowMesh } from "@asyncflowstate/core/utils/mesh";

const mesh = new FlowMesh({
  channel: "my-app-mesh",
  strategy: "leader-follower",
  shareCache: true,
  shareErrors: true,
});

Step 2: Shared State & Optimistic UI

We can now define a flow that automatically syncs its state and optimistic updates across the mesh network.

tsx
import { useCollaborativeFlow } from "@asyncflowstate/react";

function SharedTodoList() {
  const { data, execute, isLeader } = useCollaborativeFlow(
    async (newTodo) => {
      // Only the leader makes the actual API call
      const res = await fetch("/api/todos", {
        method: "POST",
        body: JSON.stringify(newTodo),
      });
      return res.json();
    },
    {
      meshChannel: "my-app-mesh",
      syncAction: true,
      optimisticResult: (newTodo) => ({ id: Math.random(), title: newTodo }),
    },
  );

  return (
    <div>
      <h3>Todos {isLeader && "(Leader)"}</h3>
      <button onClick={() => execute("New Item")}>Add Item</button>
      <ul>
        {data?.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
}

Step 3: Conflict Resolution

When multiple users try to update the same flow state simultaneously, we can use vector clocks built into CollaborativeOptions to resolve conflicts.

tsx
import { useFlow } from "@asyncflowstate/react";

function DocumentEditor() {
  const { execute } = useFlow(updateDoc, {
    collaborative: {
      channel: "doc-123",
      resolveConflict: (localData, remoteData) => {
        // Simple last-write-wins or custom merge logic
        return localData.timestamp > remoteData.timestamp
          ? localData
          : remoteData;
      },
    },
  });

  // ...
}

Built with by AsyncFlowState Contributors
Open Source · MIT License