In this tutorial, we will guide you through adding real-time synchronization to a React Flow application using SuperViz. Real-time synchronization is a key feature for collaborative applications, allowing multiple users to interact with shared content simultaneously and see each other's changes as they happen. With React Flow and SuperViz, you can build interactive flowcharts that update live, providing a seamless collaborative experience.

We'll demonstrate how to integrate contextual comments and real-time mouse pointers into a React Flow application, enabling users to collaborate on flowcharts with real-time updates. This setup allows multiple participants to add nodes, create connections, and drag elements within the flowchart, with changes visible instantly to all users in the session. Let's get started!

Prerequisite

To follow this tutorial, you will need a SuperViz account and a developer token. If you already have an account and a developer token, you can move on to the next step.

Create an account

To create an account, go to https://dashboard.superviz.com/register and create an account using either Google or an email/password. It's important to note that when using an email/password, you will receive a confirmation link that you'll need to click to verify your account.

Retrieving a Developer Token

To use the SDK, you’ll need to provide a developer token, as this token is essential for associating SDK requests with your account. You can retrieve both development and production SuperViz tokens from the dashboard..
Copy and save the developer token, as you will need it in the next steps of this tutorial.

Step 1: Set Up Your React Application

To begin, you'll need to set up a new React project where we will integrate the React Flow and SuperViz SDK for real-time collaboration.

1. Create a New React Project

First, create a new React application using Create React App with TypeScript.

1
npx create-react-app realtime-react-flow --template typescript
2
cd realtime-react-flow

2. Install Required Libraries

Next, install the necessary libraries for our project:

1
npm install @superviz/react-sdk @superviz/realtime reactflow uuid
  • @superviz/react-sdk: SuperViz SDK for integrating real-time collaboration features.
  • @superviz/realtime: SuperViz Real-Time library for integrating real-time synchronization into your application.
  • reactflow: A library for building interactive flowcharts and diagrams.
  • uuid: A library for generating unique identifiers, useful for creating unique participant IDs.

3. Configure tailwind

In this tutorial, we'll use the Tailwind css framework. First, install the tailwind package.

1
npm install -D tailwindcss postcss autoprefixer
2
npx tailwindcss init -p

We then need to configure the template path. Open tailwind.config.js in the root of the project and insert the following code.

1
/** @type {import('tailwindcss').Config} */
2
export default {
3
content: [
4
"./index.html",
5
"./src/**/*.{js,ts,jsx,tsx}",
6
],
7
theme: {
8
extend: {},
9
},
10
plugins: [],
11
}

Then we need to add the tailwind directives to the global CSS file. (src/index.css)

1
@tailwind base;
2
@tailwind components;
3
@tailwind utilities;

4. Set Up Environment Variables

Create a .env file in your project root and add your SuperViz developer key. This key will be used to authenticate your application with SuperViz services.

1
VITE_SUPERVIZ_API_KEY=YOUR_SUPERVIZ_DEVELOPER_KEY

Step 2: Implement the Main Application

In this step, we'll implement the main application logic to initialize SuperViz and handle real-time synchronization in a React Flow application.

1. Implement the App Component

Open src/App.tsx and set up the main application component using the SuperVizRoomProvider to manage the collaborative environment.

1
import { SuperVizRoomProvider } from "@superviz/react-sdk";
2
import { v4 as generateId } from "uuid";
3
import Room from "./Room";
4
import { ReactFlowProvider } from "reactflow";
5
6
const developerKey = import.meta.env.VITE_SUPERVIZ_API_KEY;
7
const participantId = generateId();
8
9
export default function App() {
10
return (
11
<SuperVizRoomProvider
12
developerKey={developerKey}
13
group={{
14
id: "react-flow-tutorial",
15
name: "react-flow-tutorial",
16
}}
17
participant={{
18
id: participantId,
19
name: "Participant",
20
}}
21
roomId="react-flow-tutorial"
22
>
23
<ReactFlowProvider>
24
<Room participantId={participantId} />
25
</ReactFlowProvider>
26
</SuperVizRoomProvider>
27
);
28
}

Explanation:

  • SuperVizRoomProvider: This component wraps the application to enable real-time features and provides configuration for group and participant details.
  • developerKey: Retrieves the developer key from environment variables to authenticate with SuperViz.
  • ReactFlowProvider: Wraps the Room component to provide React Flow's context, which manages the state of the flowchart.
  • Room Component: Contains the logic for rendering the flowchart and handling real-time interactions.

Step 3: Create the Room Component

The Room component will be responsible for integrating React Flow with SuperViz, allowing users to collaborate on the flowchart in real-time.

Step-by-Step Breakdown of the Room Component

Let's break down the Room component step-by-step to understand how it enables real-time collaboration using React Flow and SuperViz.

1. Import Necessary Modules

First, import the required modules and components from both reactflow and @superviz/react-sdk.

1
import { useCallback, useEffect, MouseEvent, useRef } from "react";
2
import ReactFlow, {
3
useNodesState,
4
Controls,
5
Background,
6
ConnectionLineType,
7
addEdge,
8
useEdgesState,
9
ConnectionMode,
10
Connection,
11
useViewport,
12
Node,
13
} from "reactflow";
14
import "reactflow/dist/style.css";
15
import {
16
Comments,
17
MousePointers,
18
useComments,
19
useHTMLPin,
20
useMouse,
21
WhoIsOnline,
22
} from "@superviz/react-sdk";
23
import { Realtime, type Channel } from "@superviz/realtime/client";

Explanation:

  • ReactFlow Imports: Provide flowchart components and utilities to create interactive diagrams.
  • SuperViz SDK Imports: Includes tools for real-time collaboration, such as comments, mouse pointers, and synchronization.

2. Define Initial Nodes and Edges

Define the initial state of nodes and edges for the flowchart.

1
type Edge = {
2
type: ConnectionLineType;
3
animated: boolean;
4
source: string | null;
5
target: string | null;
6
sourceHandle: string | null;
7
targetHandle: string | null;
8
};
9
10
const initialNodes = [
11
{ id: "1", position: { x: 381, y: 265 }, data: { label: "Start" } },
12
{ id: "2", position: { x: 556, y: 335 }, data: { label: "Action" } },
13
{ id: "3", position: { x: 701, y: 220 }, data: { label: "Process" } },
14
{ id: "4", position: { x: 823, y: 333 }, data: { label: "End" } },
15
];
16
17
const initialEdges = [
18
{
19
id: "e1-2",
20
source: "1",
21
target: "2",
22
type: ConnectionLineType.SmoothStep,
23
animated: true,
24
},
25
{
26
id: "e2-3",
27
source: "2",
28
target: "3",
29
type: ConnectionLineType.SmoothStep,
30
animated: true,
31
},
32
{
33
id: "e3-4",
34
source: "3",
35
target: "4",
36
type: ConnectionLineType.SmoothStep,
37
animated: true,
38
},
39
];

Explanation:

  • Edge Type: Defines the structure of an edge in the flowchart.
  • initialNodes: An array of objects defining each node's position and label in the flowchart.
  • initialEdges: An array of objects defining connections between nodes, using a smooth step connection line with animation.

3. Define the Room Component

Create the Room component with properties to manage the participant ID.

1
type Props = {
2
participantId: string;
3
};
4
5
export default function Room({ participantId }: Props) {
6
const initialized = useRef(false);
7
const channel = useRef<Channel>();

Explanation:

  • Props Type: Defines the expected properties for the Room component, including the participantId.
  • initialized Ref: A ref to track whether the component has initialized the Real-Time component and subscribed to real-time events, ensuring it only happens once.
  • Channel Ref: A ref to store the real-time channel for communication between participants.

4. Set Up Real-Time Hooks and Utilities

Initialize SuperViz hooks and utilities to manage comments, real-time updates, and mouse transformations.

1
// Managing comments
2
const { openThreads, closeThreads } = useComments();
3
4
// Managing real-time updates
5
const { isReady, subscribe, unsubscribe, publish } = useRealtime("default");
6
7
// Managing mouse pointers
8
const { transform } = useMouse();
9
10
// Pinning functionality for comments
11
const { pin } = useHTMLPin({
12
containerId: "react-flow-container",
13
dataAttributeName: "data-id",
14
dataAttributeValueFilters: [/.*null-(target|source)$/],
15
});

Explanation:

  • useComments: Provides functions to open and close comment threads.
  • useRealtime: Offers real-time event handling methods like subscribe, unsubscribe, and publish.
  • useMouse: Allows transformations based on mouse movements.
  • useHTMLPin: Enables the pinning of comments to specific HTML elements within the application.

5. Initialize State for Nodes and Edges

Manage the state of nodes and edges using React Flow's hooks.

1
const { x, y, zoom } = useViewport();
2
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
3
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

Explanation:

  • useViewport: Provides the current viewport state, including the x and y translation and zoom level.
  • useNodesState: Manages the state of nodes with initial values and handles changes.
  • useEdgesState: Manages the state of edges with initial values and handles changes.

6. Handle Edge Connections

Define a callback function for handling new edge connections in the flowchart.

1
const onConnect = useCallback(
2
(connection: Connection) => {
3
const edge: Edge = {
4
...connection,
5
type: ConnectionLineType.SmoothStep,
6
animated: true,
7
};
8
9
setEdges((eds) => addEdge(edge, eds));
10
11
channel.current!.publish("new-edge", {
12
edge,
13
});
14
},
15
[setEdges]
16
);

Explanation:

  • onConnect: Handles the creation of new edges, updating the state and publishing the changes to all participants.
  • addEdge: Adds a new edge to the current state.

7. Handle Node Dragging

Create a callback for handling node dragging events and publish the changes.

1
const onNodeDrag = useCallback(
2
(_: MouseEvent, node: Node) => {
3
publish("node-drag", { node });
4
},
5
[publish]
6
);

Explanation:

  • onNodeDrag: Publishes node position updates as they are dragged, allowing other participants to see the changes.

8. Handle Drag Over Events

Prevent the default behavior for drag-over events to enable custom dragging interactions.

1
const onDragOver = useCallback(
2
(event: React.DragEvent<HTMLButtonElement | HTMLDivElement>) => {
3
event.preventDefault();
4
event.dataTransfer.dropEffect = "move";
5
},
6
[]
7
);

Explanation:

  • onDragOver: Sets the drag effect to "move" to provide visual feedback during drag operations.

9. Update Viewport on Mouse Movement

Adjust the viewport based on mouse movements to keep the flowchart aligned.

1
useEffect(() => {
2
transform({
3
translate: {
4
x: x,
5
y: y,
6
},
7
scale: zoom,
8
});
9
}, [x, y, zoom, transform]);

Explanation:

  • useEffect: Transforms the viewport position and scale when the mouse moves, ensuring all participants have a synchronized view.

10. Set Data Attribute for SuperViz Pinning

Assign a data attribute to the React Flow container for SuperViz pinning.

1
useEffect(() => {
2
const element = document.querySelector(".react-flow__pane");
3
4
if (!element) return;
5
6
element.setAttribute("data-superviz-id", "plane");
7
}, []);

Explanation:

  • data-superviz-id: Allows SuperViz to identify elements that can have pins attached, facilitating comment features.

11. Initialize Real-Time Component

Start Real-Time, connect to a channel and set up subscriptions to listen for real-time events and synchronize state changes.

1
const initializeRealtime = useCallback(async () => {
2
if (initialized.current) return;
3
initialized.current = true;
4
5
const realtime = new Realtime(developerKey, {
6
participant: {
7
id: participantId,
8
name: "Participant",
9
},
10
});
11
12
channel.current = await realtime.connect("react-flow-tutorial");
13
14
const centerNodes = () => {
15
const centerButton = document.querySelector(
16
".react-flow__controls-fitview"
17
) as HTMLButtonElement;
18
centerButton?.click();
19
};
20
21
centerNodes();
22
23
channel.current!.subscribe<{ edge: Edge }>(
24
"new-edge",
25
({ data, participantId: senderId }) => {
26
if (senderId === participantId) return;
27
28
setEdges((eds) => addEdge(data.edge, eds));
29
}
30
);
31
32
channel.current!.subscribe<{ node: Node }>(
33
"node-drag",
34
({ data, participantId: senderId }) => {
35
if (senderId === participantId) return;
36
37
setNodes((nds) =>
38
nds.map((node) =>
39
node.id === data.node.id ? { ...node, ...data.node } : node
40
)
41
);
42
}
43
);
44
}, [participantId, setEdges, setNodes]);
45
46
useEffect(() => {
47
initializeRealtime();
48
49
return () => {
50
channel.current?.disconnect();
51
};
52
}, [initializeRealtime]);

Explanation:

  • initializeRealtime: Use the user API key and participant infos to set up a new Real-Time connection.Listens for specific events (new-edge, node-drag) and updates the local state based on incoming data.
  • disconnect: Clean up subscriptions when no longer needed.

12. Render the Room Component

Finally, render the Room component with React Flow and SuperViz features.

1
return (
2
<div className="w-full h-full bg-gray-200 flex items-center justify-center flex-col">
3
<header className="w-full p-5 bg-purple-400 flex items-center justify-between">
4
<h1 className="text-white text-2xl font-bold">React Flow + SuperViz</h1>
5
<div id="comments" className="flex gap-2"></div>
6
</header>
7
<main className="flex-1 w-full h-full">
8
<div id="react-flow-container" className="w-full h-full">
9
<ReactFlow
10
nodes={nodes}
11
onNodeDrag={onNodeDrag}
12
edges={edges}
13
onConnect={onConnect}
14
onNodesChange={onNodesChange}
15
onEdgesChange={onEdgesChange}
16
onDragOver={onDragOver}
17
connectionMode={ConnectionMode.Loose}
18
>
19
<Controls showFitView={false} />
20
<Background />
21
</ReactFlow>
22
</div>
23
24
{/* SuperViz Components */}
25
<WhoIsOnline position="comments" />
26
<Comments
27
pin={pin}
28
position="left"
29
buttonLocation="comments"
30
onPinActive={openThreads}
31
onPinInactive={closeThreads}
32
/>
33
<MousePointers elementId="react-flow-container" />
34
</main>
35
</div>
36
);

Explanation:

  • ReactFlow: Displays the flowchart with nodes, edges, and interaction handlers.
  • Realtime: Manages real-time synchronization of state across participants.
  • WhoIsOnline: Shows a list of online participants in the session.
  • Comments: Provides the ability to add and view contextual comments.
  • MousePointers: Displays real-time mouse pointers for all participants.

Step 4: Running the Application

1. Start the React Application

To run your application, use the following command in your project directory:

1
npm run dev

This command will start the development server and open your application in the default web browser. You can interact with the flowchart and see updates in real-time across multiple participants.

2. Test the Application

  • Collaborative Flowchart: Open the application in multiple browser windows or tabs to simulate multiple participants and verify that changes made by one participant are reflected in real-time for others.
  • Real-Time Updates: Test the responsiveness of the application to see if it syncs correctly with actions performed by other users.

Summary

In this tutorial, we built a collaborative flowchart application using React Flow and SuperViz for real-time synchronization. We configured a React application to handle node and edge interactions, enabling multiple users to collaborate seamlessly on a shared diagram. This setup can be extended and customized to fit various scenarios where real-time collaboration and workflow visualization are required.

Feel free to explore the full code and further examples in the GitHub repository for more details.