@@ -217,6 +217,7 @@ function FlowInner({
217217 model,
218218 hydratedNodes,
219219 pyNodes,
220+ nodeUpdateCount,
220221 hydratedEdges,
221222 selectionSetter,
222223 currentSelection,
@@ -242,7 +243,9 @@ function FlowInner({
242243 const [ edges , setEdges , onEdgesChange ] = useEdgesState ( hydratedEdges ) ;
243244 const nodesRef = useRef ( nodes ) ;
244245 const edgesRef = useRef ( edges ) ;
245- const lastHydrated = useRef ( { nodesSig : null , viewsRef : null , editorsRef : null , edgesSig : null , edgeEditorsSig : null } ) ;
246+ const hydrationFrameRef = useRef ( null ) ;
247+ const edgeHydrationFrameRef = useRef ( null ) ;
248+ const lastHydrated = useRef ( { nodeRevision : null , nodesSig : null , edgesSig : null , edgeEditorsSig : null } ) ;
246249 const lastViewportSig = useRef ( null ) ;
247250 const { setViewport : setRfViewport } = useReactFlow ( ) ;
248251
@@ -291,45 +294,67 @@ function FlowInner({
291294 } , [ edges ] ) ;
292295
293296 useEffect ( ( ) => {
294- const readyByNodeId = new Map (
295- ( hydratedNodes || [ ] ) . map ( ( node ) => [ node ?. id , Boolean ( node ?. data ?. _viewReady ) ] ) ,
296- ) ;
297- const pyNodesWithReady = ( pyNodes || [ ] ) . map ( ( node ) => ( {
298- ...node ,
299- _viewReady : readyByNodeId . get ( node ?. id ) ?? true ,
300- } ) ) ;
301- const nodesSig = signature ( pyNodesWithReady ) ;
302- const viewsSig = signature ( ( views || [ ] ) . map ( ( view ) => view ?. props ?. id ?? null ) ) ;
303- const editorsSig = signature ( ( nodeEditors || [ ] ) . map ( ( editor ) => editor ?. props ?. id ?? null ) ) ;
304- if ( nodesSig === lastHydrated . current . nodesSig && viewsSig === lastHydrated . current . viewsRef && editorsSig === lastHydrated . current . editorsRef ) {
297+ return ( ) => {
298+ if ( hydrationFrameRef . current !== null ) {
299+ cancelAnimationFrame ( hydrationFrameRef . current ) ;
300+ hydrationFrameRef . current = null ;
301+ }
302+ if ( edgeHydrationFrameRef . current !== null ) {
303+ cancelAnimationFrame ( edgeHydrationFrameRef . current ) ;
304+ edgeHydrationFrameRef . current = null ;
305+ }
306+ } ;
307+ } , [ ] ) ;
308+
309+ useEffect ( ( ) => {
310+ const nodesSig = signature ( hydratedNodes ) ;
311+ if (
312+ nodeUpdateCount === lastHydrated . current . nodeRevision &&
313+ nodesSig === lastHydrated . current . nodesSig
314+ ) {
305315 return ;
306316 }
307- lastHydrated . current . nodesSig = nodesSig ;
308- lastHydrated . current . viewsRef = viewsSig ;
309- lastHydrated . current . editorsRef = editorsSig ;
310-
311- setNodes ( ( curr ) => {
312- const currById = new Map ( curr . map ( ( n ) => [ n . id , n ] ) ) ;
313- const merged = hydratedNodes . map ( ( n ) => {
314- const prev = currById . get ( n . id ) ;
315- if ( ! prev ) return n ;
316- return {
317- ...n ,
318- selected : prev . selected ,
319- dragging : prev . dragging ,
320- } ;
317+
318+ if ( hydrationFrameRef . current !== null ) {
319+ cancelAnimationFrame ( hydrationFrameRef . current ) ;
320+ }
321+ hydrationFrameRef . current = requestAnimationFrame ( ( ) => {
322+ setNodes ( ( curr ) => {
323+ const currById = new Map ( curr . map ( ( n ) => [ n . id , n ] ) ) ;
324+ const merged = hydratedNodes . map ( ( n ) => {
325+ const prev = currById . get ( n . id ) ;
326+ if ( ! prev ) return n ;
327+ const next = {
328+ ...n ,
329+ selected : prev . selected ,
330+ dragging : prev . dragging ,
331+ } ;
332+ return areEqual ( prev , next ) ? prev : next ;
333+ } ) ;
334+ if ( merged . length === curr . length && merged . every ( ( node , index ) => node === curr [ index ] ) ) {
335+ return curr ;
336+ }
337+ return merged ;
321338 } ) ;
322- return merged ;
339+ lastHydrated . current . nodeRevision = nodeUpdateCount ;
340+ lastHydrated . current . nodesSig = nodesSig ;
341+ hydrationFrameRef . current = null ;
323342 } ) ;
324- } , [ hydratedNodes , pyNodes , setNodes , views , nodeEditors ] ) ;
343+ } , [ hydratedNodes , pyNodes , setNodes , views , nodeEditors , nodeUpdateCount ] ) ;
325344
326345 useEffect ( ( ) => {
327346 const edgesSig = signature ( hydratedEdges ) ;
328347 const editorsSig = signature ( ( edgeEditors || [ ] ) . map ( ( editor ) => editor ?. props ?. id ?? null ) ) ;
329348 if ( edgesSig !== lastHydrated . current . edgesSig || editorsSig !== lastHydrated . current . edgeEditorsSig ) {
330349 lastHydrated . current . edgesSig = edgesSig ;
331350 lastHydrated . current . edgeEditorsSig = editorsSig ;
332- setEdges ( hydratedEdges ) ;
351+ if ( edgeHydrationFrameRef . current !== null ) {
352+ cancelAnimationFrame ( edgeHydrationFrameRef . current ) ;
353+ }
354+ edgeHydrationFrameRef . current = requestAnimationFrame ( ( ) => {
355+ setEdges ( ( curr ) => ( areEqual ( curr , hydratedEdges ) ? curr : hydratedEdges ) ) ;
356+ edgeHydrationFrameRef . current = null ;
357+ } ) ;
333358 }
334359 } , [ hydratedEdges , setEdges , edgeEditors ] ) ;
335360
@@ -409,32 +434,6 @@ function FlowInner({
409434 const onNodesDelete = useCallback (
410435 ( deletedNodes ) => {
411436 const deletedIds = deletedNodes . map ( ( node ) => node . id ) ;
412- const deletedViewIdx = deletedNodes
413- . map ( ( node ) => node ?. data ?. view_idx )
414- . filter ( ( value ) => Number . isFinite ( value ) )
415- . sort ( ( a , b ) => a - b ) ;
416- if ( deletedViewIdx . length ) {
417- const deletedSet = new Set ( deletedIds ) ;
418- setNodes ( ( current ) =>
419- current . map ( ( node ) => {
420- if ( deletedSet . has ( node . id ) ) {
421- return node ;
422- }
423- const viewIdx = node ?. data ?. view_idx ;
424- if ( ! Number . isFinite ( viewIdx ) ) {
425- return node ;
426- }
427- const shift = deletedViewIdx . filter ( ( idx ) => idx < viewIdx ) . length ;
428- if ( ! shift ) {
429- return node ;
430- }
431- return {
432- ...node ,
433- data : { ...node . data , view_idx : viewIdx - shift } ,
434- } ;
435- } ) ,
436- ) ;
437- }
438437 const deletedEdges = edgesRef . current . filter ( ( edge ) => deletedIds . includes ( edge . source ) || deletedIds . includes ( edge . target ) ) ;
439438 schedulePatch ( {
440439 type : "node_deleted" ,
@@ -443,7 +442,7 @@ function FlowInner({
443442 deleted_edges : deletedEdges . map ( ( edge ) => edge . id ) ,
444443 } ) ;
445444 } ,
446- [ schedulePatch , setNodes ] ,
445+ [ schedulePatch ] ,
447446 ) ;
448447
449448 const onEdgesDelete = useCallback (
@@ -500,6 +499,7 @@ export function render({ model, view }) {
500499 const [ readyViewMap , setReadyViewMap ] = useState ( ( ) => new Map ( ) ) ;
501500 const readyCheckTimeoutsRef = useRef ( new Map ( ) ) ;
502501 const [ pyNodes ] = model . useState ( "nodes" ) ;
502+ const [ nodeUpdateCount ] = model . useState ( "_node_update_count" ) ;
503503 const [ pyEdges ] = model . useState ( "edges" ) ;
504504 const [ pyNodeTypes ] = model . useState ( "node_types" ) ;
505505 const [ defaultEdgeOptions ] = model . useState ( "default_edge_options" ) ;
@@ -614,18 +614,19 @@ export function render({ model, view }) {
614614 return ( pyNodes || [ ] ) . map ( ( node , idx ) => {
615615 const data = node . data || { } ;
616616 const viewIndex = data . view_idx ;
617+ const { view_idx, ...dataWithoutViewIdx } = data ;
617618 const baseView = views [ viewIndex ] ;
618619 const baseViewId = baseView ?. key ;
619620 const isViewReady = baseViewId ? Boolean ( readyViewMap . get ( baseViewId ) ) : true ;
620621 const editorView = nodeEditors [ idx ] ;
621622 const typeSpec = allNodeTypes [ node . type ] || { } ;
622- const realKeys = Object . keys ( data ) . filter ( ( k ) => k !== "view_idx" ) ;
623+ const realKeys = Object . keys ( dataWithoutViewIdx ) ;
623624 const hasEditor = realKeys . length > 0 || ! ! typeSpec . schema ;
624625 return {
625626 ...node ,
626627 className : ( node . type === "panel" || model . stylesheets . length > 7 ) ? "" : "react-flow__node-default" ,
627628 data : {
628- ...data ,
629+ ...dataWithoutViewIdx ,
629630 view : baseView ,
630631 editor : editorView ,
631632 _viewReady : isViewReady ,
@@ -634,7 +635,7 @@ export function render({ model, view }) {
634635 } ,
635636 } ;
636637 } ) ;
637- } , [ pyNodes , nodeEditors , views , editorMode , allNodeTypes , readyViewMap ] ) ;
638+ } , [ pyNodes , nodeEditors , views , editorMode , allNodeTypes ] ) ;
638639
639640 const hydratedEdges = useMemo ( ( ) => {
640641 return ( pyEdges || [ ] ) . map ( ( edge ) => {
@@ -662,6 +663,7 @@ export function render({ model, view }) {
662663 model = { model }
663664 hydratedNodes = { hydratedNodes }
664665 pyNodes = { pyNodes || [ ] }
666+ nodeUpdateCount = { nodeUpdateCount }
665667 hydratedEdges = { hydratedEdges }
666668 selectionSetter = { setSelection }
667669 currentSelection = { selection }
0 commit comments