Skip to content

mindmap.build / react architecture

The unreasonable effectiveness of React in mindmap.build

This architectural catalog outlines the core React patterns, custom hooks, dynamic layout strategies, and performance shortcuts that keep our infinite mind mapping studio canvas smooth and responsive.

State ControluseReducer Switchboard
DOM BindingsResizeObserver Loops
Render Bypassesstable callbacks + memo
CompositingGPU translation
01

useReducer State Switchboard

A single source of truth for the entire mindmap coordinate grid. Rather than orchestrating dozens of disjointed useState and useEffect hooks, mindmap.build uses a central reducer to manage nodes, links, selection, layout configurations, and historical stacks.

Why It Matters

Prevents out-of-sync states (like nodes with missing parent indices or invalid edge keys) by forcing all layout and node operations to be processed as transaction payloads.

SymbolmindMapReducer
Locationmodules/studio/store/mind-map-reducer.ts

Implementation

ts
export const mindMapReducer = (state, action) => {
  switch (action.type) {
    case 'LOAD_GRAPH':
      return handleLoadGraph(state, action.payload)
    case 'REPARENT_NODE':
      return handleReparentNode(state, action.payload)
    case 'MOVE_NODES':
      return handleMoveNodes(state, action.payload)
    case 'UNDO':
      return handleUndo(state)
    default:
      return state
  }
}
02

useRef + useLayoutEffect Bypass

mindmap.build passes event handlers down to dozens of interactive mindmap nodes. To prevent layout thrashing and unnecessary renders, these callback functions must remain stable. We bypass the React rendering tree by updating a mutable handlersRef inside useLayoutEffect, passing down standard, immutable wrapper callbacks.

Why It Matters

Without this ref bypass, every keystroke in a node editor would re-allocate new callback instances, causing all sibling nodes on the infinite canvas to execute reconciliation, leading to typing lag.

SymbolhandlersRef
Locationmodules/studio/components/Studio.tsx
ReferenceStudio.tsx

Implementation

ts
const handlersRef = useRef({
  onMouseDown: handleNodeMouseDown,
  onUpdateText: updateNodeText,
})

useLayoutEffect(() => {
  handlersRef.current = {
    onMouseDown: handleNodeMouseDown,
    onUpdateText: updateNodeText,
  }
}, [handleNodeMouseDown, updateNodeText])

const stableCallbacks = useMemo(
  () => ({
    onMouseDown: (e, id) => handlersRef.current.onMouseDown(e, id),
    onUpdateText: (id, txt) => handlersRef.current.onUpdateText(id, txt),
  }),
  []
)
StateRef Box
03

Decoupled Hooks Architecture

Infinite canvas apps represent multiple complex subsystems running concurrently. mindmap.build isolates these concerns into specialized custom hooks that coordinate with the central state and UI ref pointers.

Why It Matters

Maintains clean code isolation. Viewport matrices, drag-reparent mathematics, keyboard event handlers, and data syncing do not bleed into the render layers.

SymboluseStudioViewport
Locationmodules/studio/hooks/useStudioViewport.ts

Implementation

ts
// Hooks Entry point index.ts
export { useStudioViewport } from './useStudioViewport'
export { useStudioCanvasInteractions } from './useStudioCanvasInteractions'
export { useStudioCommands } from './useStudioCommands'
export { useStudioKeyboardShortcuts } from './useStudioKeyboardShortcuts'
export { useStudioRenderModel } from './useStudioRenderModel'
ViewportDragSyncSTUDIO CANVAS
04

DOM Measurements & Loop Breakers

Mindmap layout algorithms (like tree balancing) cannot calculate node positions without knowing the actual text wrap dimensions. mindmap.build uses ResizeObserver on node elements to capture exact dimensions and sync them back to the state container.

Why It Matters

Resolves the "measure-render" loop hazard. By using requestAnimationFrame and checking dimension differentials, mindmap.build breaks layout calculation cycles that cause page freezes.

SymbolResizeObserver
Locationmodules/studio/components/MindMapNode.tsx

Implementation

ts
const observer = new ResizeObserver((entries) => {
  const entry = entries.find((e) => e.target === element)
  const dimensions = measureStudioNodeElement(element, entry)
  if (dimensions) {
    onMeasureNode(node.id, dimensions)
  }
})
observer.observe(element, { box: 'border-box' })
DOM
05

CSS Grid Auto-Expanding Stack

Standard HTML textareas do not grow vertically to match typing height. mindmap.build layers an invisible copy of the node text (a ghost wrapper) directly beneath the active textarea input within a single grid cell.

Why It Matters

Eliminates layout lag. Using pure CSS grids allows the browser to perform calculations during the layout reflow pipeline, keeping frame rates at 60fps.

SymbolRichTextNodeEditor
Locationmodules/studio/components/MindMapNode.tsx

Implementation

html
<div className="grid place-items-center w-full relative">
  {/* GHOST WRAPPER: Grows grid cell organically */}
  <div className="col-start-1 row-start-1 invisible whitespace-pre-wrap break-word">
    {displayText || '\u00A0'}
  </div>

  {/* ACTIVE INPUT: Stretches to fill container bounds */}
  <RichTextNodeEditor className="col-start-1 row-start-1 absolute inset-0 w-full h-full" />
</div>
Invisible Ghost TextActive Textarea Input
06

React Portals & Context Menus

Mindmap canvases use heavy styling transforms like translate and scale. Any child menus, styles lists, or tools positioned inside them will be cut off by layout masks. Radix UI primitives use React Portals to break elements out of these layouts.

Why It Matters

Guarantees float widgets and overlay dropdown panels maintain correct positions and pixel-perfect sizes without getting clipped by canvas boundaries.

SymbolContextMenu
Locationmodules/studio/components/MindMapNode.tsx

Implementation

ts
import { createPortal } from 'react-dom'

export function PortalOverlay({ children }) {
  // Pulls overlay content out of canvas context
  // and mounts it directly into document.body
  return createPortal(
    <div className="absolute z-50 float-menu">
      {children}
    </div>,
    document.body
  )
}
Canvas MaskPortal Menu
07

React.memo & GPU Acceleration

Re-rendering hundreds of canvas components during panning causes layout latency. mindmap.build prevents useless reconciliations by wrapping nodes in React.memo and moving positions using GPU compositing transforms.

Why It Matters

Shifts node coordinates directly to the GPU compositor via composite layering transforms, bypassing standard CPU-intensive render trees.

SymbolMindMapNodeComponent
Locationmodules/studio/components/MindMapNode.tsx

Implementation

ts
export const MindMapNodeComponent = React.memo(
  (props) => {
    // Renders node contents...
  },
  (prevProps, nextProps) => {
    // Custom equality checks to avoid re-rendering
    return (
      prevProps.isSelected === nextProps.isSelected &&
      prevProps.isEditing === nextProps.isEditing &&
      prevProps.node.x === nextProps.node.x &&
      prevProps.node.y === nextProps.node.y &&
      prevProps.node.text === nextProps.node.text
    )
  }
)
memoized
08

Batch Updates & History Stacks

Mutating multiple coordinates concurrently (e.g., resizing nodes or multi-selection stylings) creates a history footprint. We isolate transformations into transactional boundaries using batch markers.

Why It Matters

Ensures that undoing a drag operation moves the entire group of nodes back to their starting positions collectively, rather than reversing one pixel coordinate at a time.

SymbolBEGIN_BATCH_UPDATE
Locationmodules/studio/store/mind-map-reducer.ts

Implementation

ts
case 'BEGIN_BATCH_UPDATE':
  return {
    ...state,
    history: [...state.history, createHistorySnapshot(state)],
    future: [],
  }

case 'COMMIT_BATCH_UPDATE':
  // Finishes batch sequence and solidifies state
  return state;
Hist