React Flow
This is an example of real-time collaborative React Flow.
App.tsx
1import { useCallback, useRef } from 'react';2import { JSONArray, useDocument } from '@yorkie-js/react';3import {4 ReactFlow,5 Node,6 Edge,7 Background,8 NodeChange,9 EdgeChange,10 Connection,11} from '@xyflow/react';12import '@xyflow/react/dist/style.css';13import './App.css';1415type Graph = {16 nodes: JSONArray<Node>;17 edges: JSONArray<Edge>;18};1920function App() {21 const { root, update, loading, error } = useDocument<Graph>();22 // simple incremental id for new edges23 const edgeIdRef = useRef(0);2425 const onNodesChange = useCallback(26 (changes: Array<NodeChange>) => {27 update((r) => {28 for (const c of changes) {29 switch (c.type) {30 case 'add':31 r.nodes.push(c.item);32 break;33 case 'replace':34 {35 const idx = r.nodes.findIndex((n) => n.id === c.id);36 if (idx !== -1) {37 r.nodes[idx] = c.item;38 }39 }40 break;41 case 'remove':42 {43 const idx = r.nodes.findIndex((n) => n.id === c.id);44 if (idx !== -1) {45 r.nodes.delete?.(idx);46 }47 }48 break;49 case 'position':50 {51 const idx = r.nodes.findIndex((n) => n.id === c.id);52 if (idx !== -1 && c.position) {53 r.nodes[idx].position = c.position;54 }55 }56 break;57 case 'select':58 {59 const idx = r.nodes.findIndex((n) => n.id === c.id);60 if (idx !== -1) {61 r.nodes[idx].selected = c.selected;62 }63 }64 break;65 default:66 break;67 }68 }69 });70 },71 [update],72 );7374 const onEdgesChange = useCallback(75 (changes: Array<EdgeChange>) => {76 update((r) => {77 for (const c of changes) {78 switch (c.type) {79 case 'add':80 r.edges.push(c.item);81 break;82 case 'replace':83 {84 const idx = r.edges.findIndex((e) => e.id === c.id);85 if (idx !== -1) {86 r.edges[idx] = c.item;87 }88 }89 break;90 case 'remove':91 {92 const idx = r.edges.findIndex((e) => e.id === c.id);93 if (idx !== -1) {94 r.edges.delete?.(idx);95 }96 }97 break;98 case 'select':99 {100 const idx = r.edges.findIndex((e) => e.id === c.id);101 if (idx !== -1) {102 r.edges[idx].selected = c.selected;103 }104 }105 break;106 default:107 break;108 }109 }110 });111 },112 [update],113 );114115 const onConnect = useCallback(116 (connection: Connection) => {117 if (!connection.source || !connection.target) return;118 update((r) => {119 const already = r.edges.some(120 (e) =>121 e.source === connection.source &&122 e.target === connection.target &&123 e.sourceHandle === connection.sourceHandle &&124 e.targetHandle === connection.targetHandle,125 );126 if (already) return;127 const id = `e-${connection.source}-${128 connection.target129 }-${edgeIdRef.current++}`;130 r.edges.push({131 id,132 type: 'smoothstep',133 source: connection.source,134 target: connection.target,135 sourceHandle: connection.sourceHandle,136 targetHandle: connection.targetHandle,137 });138 });139 },140 [update],141 );142143 if (loading) return <div>Loading...</div>;144 if (error) return <div>Error: {error.message}</div>;145146 return (147 <div style={{ position: 'fixed', inset: 0, height: '100vh' }}>148 <ReactFlow149 nodes={[...root.nodes].filter(Boolean) as Node[]}150 edges={[...root.edges].filter(Boolean) as Edge[]}151 onNodesChange={onNodesChange}152 onEdgesChange={onEdgesChange}153 onConnect={onConnect}154 fitView155 >156 <Background gap={10} size={1} color="silver" />157 </ReactFlow>158 </div>159 );160}161162export default App;
- User 1
- User 2
- User 1
- User 2