Hello everyone 👋🏼
Today's tutorial 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 instantly visible to all session users.
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.
npx create-react-app realtime-react-flow --template typescript
cd realtime-react-flow
2. Install Required Libraries
Next, install the necessary libraries for our project:
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.
npm install -D tailwindcss postcss autoprefixer
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.
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
Then we need to add the tailwind directives to the global CSS file. (src/index.css)
@tailwind base;
@tailwind components;
@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.
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.
import { SuperVizRoomProvider } from "@superviz/react-sdk";
import { v4 as generateId } from "uuid";
import Room from "./Room";
import { ReactFlowProvider } from "reactflow";
const developerKey = import.meta.env.VITE_SUPERVIZ_API_KEY;
const participantId = generateId();
export default function App() {
return (
<SuperVizRoomProvider
developerKey={developerKey}
group={{
id: "react-flow-tutorial",
name: "react-flow-tutorial",
}}
participant={{
id: participantId,
name: "Participant",
}}
roomId="react-flow-tutorial"
>
<ReactFlowProvider>
<Room participantId={participantId} />
</ReactFlowProvider>
</SuperVizRoomProvider>
);}
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
.
import { useCallback, useEffect, MouseEvent, useRef } from "react";
import ReactFlow, {
useNodesState,
Controls,
Background,
ConnectionLineType,
addEdge,
useEdgesState,
ConnectionMode,
Connection,
useViewport,
Node,
} from "reactflow";
import "reactflow/dist/style.css";
import {
Comments,
MousePointers,
useComments,
useHTMLPin,
useMouse,
WhoIsOnline,
} from "@superviz/react-sdk";
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.
type Edge = {
type: ConnectionLineType;
animated: boolean;
source: string | null;
target: string | null;
sourceHandle: string | null;
targetHandle: string | null;
};
const initialNodes = [
{ id: "1", position: { x: 381, y: 265 }, data: { label: "Start" } },
{ id: "2", position: { x: 556, y: 335 }, data: { label: "Action" } },
{ id: "3", position: { x: 701, y: 220 }, data: { label: "Process" } },
{ id: "4", position: { x: 823, y: 333 }, data: { label: "End" } },
];
const initialEdges = [
{
id: "e1-2",
source: "1",
target: "2",
type: ConnectionLineType.SmoothStep,
animated: true,
},
{
id: "e2-3",
source: "2",
target: "3",
type: ConnectionLineType.SmoothStep,
animated: true,
},
{
id: "e3-4",
source: "3",
target: "4",
type: ConnectionLineType.SmoothStep,
animated: true,
},
];
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.
type Props = {
participantId: string;
};
export default function Room({ participantId }: Props) {
const initialized = useRef(false);
const channel = useRef<Channel>();
Explanation:
- Props Type: Defines the expected properties for the
Room
component, including theparticipantId
. - 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.
// Managing comments
const { openThreads, closeThreads } = useComments();
// Managing real-time updates
const { isReady, subscribe, unsubscribe, publish } = useRealtime("default");
// Managing mouse pointers
const { transform } = useMouse(); /
/ Pinning functionality for comments
const { pin } = useHTMLPin({
containerId: "react-flow-container",
dataAttributeName: "data-id",
dataAttributeValueFilters: [/.*null-(target|source)$/],
});
Explanation:
- useComments: Provides functions to open and close comment threads.
- useRealtime: Offers real-time event handling methods like
subscribe
,unsubscribe
, andpublish
. - 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.
const { x, y, zoom } = useViewport();
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
Explanation:
- useViewport: Provides the current viewport state, including the
x
andy
translation andzoom
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.
const onConnect = useCallback(
(connection: Connection) => {
const edge: Edge = {
...connection,
type: ConnectionLineType.SmoothStep,
animated: true,
};
setEdges((eds) => addEdge(edge, eds));
channel.current!.publish("new-edge", {
edge,
});
},
[setEdges]
);
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.
const onNodeDrag = useCallback(
(_: MouseEvent, node: Node) => {
publish("node-drag", { node });
},
[publish]
);
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.
const onDragOver = useCallback(
(event: React.DragEvent<HTMLButtonElement | HTMLDivElement>) => {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
},
[]
);
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.
useEffect(() => {
transform({
translate: {
x: x,
y: y,
},
scale: zoom,
});
}, [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.
useEffect(() => {
const element = document.querySelector(".react-flow__pane");
if (!element) return;
element.setAttribute("data-superviz-id", "plane");
}, []);
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.
const initializeRealtime = useCallback(async () => {
if (initialized.current) return;
initialized.current = true;
const realtime = new Realtime(developerKey, {
participant: {
id: participantId,
name: "Participant",
},
});
channel.current = await realtime.connect("react-flow-tutorial");
const centerNodes = () => {
const centerButton = document.querySelector(
".react-flow__controls-fitview"
) as HTMLButtonElement;
centerButton?.click();
};
centerNodes();
channel.current!.subscribe<{ edge: Edge }>(
"new-edge",
({ data, participantId: senderId }) => {
if (senderId === participantId) return;
setEdges((eds) => addEdge(data.edge, eds));
}
);
channel.current!.subscribe<{ node: Node }>(
"node-drag",
({ data, participantId: senderId }) => {
if (senderId === participantId) return;
setNodes((nds) =>
nds.map((node) =>
node.id === data.node.id ? { ...node, ...data.node } : node
)
);
}
);
}, [participantId, setEdges, setNodes]);
useEffect(() => {
initializeRealtime();
return () => {
channel.current?.disconnect();
};
}, [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.
return (
<div className="w-full h-full bg-gray-200 flex items-center justify-center flex-col">
<header className="w-full p-5 bg-purple-400 flex items-center justify-between">
<h1 className="text-white text-2xl font-bold">React Flow + SuperViz</h1>
<div id="comments" className="flex gap-2"></div>
</header>
<main className="flex-1 w-full h-full">
<div id="react-flow-container" className="w-full h-full">
<ReactFlow
nodes={nodes}
onNodeDrag={onNodeDrag}
edges={edges}
onConnect={onConnect}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onDragOver={onDragOver}
connectionMode={ConnectionMode.Loose}
>
<Controls showFitView={false} />
<Background />
</ReactFlow>
</div>
{/* SuperViz Components */}
<WhoIsOnline position="comments" />
<Comments
pin={pin}
position="left"
buttonLocation="comments"
onPinActive={openThreads}
onPinInactive={closeThreads}
/>
<MousePointers elementId="react-flow-container" />
</main>
</div>
);
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:
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.
Join us: SuperViz Hackathon 2.0: AI meets RealTime
Get ready to participate in our Super Hackathon 2.0 this October to showcase your innovative real-time AI communication solutions! Hosted by SuperViz and with $5,000 in prizes up for grabs, we challenge you to integrate AI with SuperViz's real-time platform, pushing the boundaries of interactive communication.
Ready to join? Visit the Super Hackathon 2.0 website to register and stay updated.