import { useVisibleNodeIds } from '../../hooks/useVisibleNodeIds'; import { useStore } from '../../hooks/useStore'; import { containerStyle } from '../../styles/utils'; import { GraphViewProps } from '../GraphView'; import { useResizeObserver } from './useResizeObserver'; import { NodeWrapper } from '../../components/NodeWrapper'; import type { Node, SolidFlowState } from '../../types'; import { For } from 'solid-js'; export type NodeRendererProps = Pick< GraphViewProps, | 'onNodeClick' | 'onNodeDoubleClick' | 'onNodeMouseEnter' | 'onNodeMouseMove' | 'onNodeMouseLeave' | 'onNodeContextMenu' | 'onlyRenderVisibleElements' | 'noPanClassName' | 'noDragClassName' | 'rfId' | 'disableKeyboardA11y' | 'nodeOrigin' | 'nodeExtent' | 'nodeTypes' >; const selector = (s: SolidFlowState) => ({ nodesDraggable: s.nodesDraggable, nodesConnectable: s.nodesConnectable, nodesFocusable: s.nodesFocusable, elementsSelectable: s.elementsSelectable, onError: s.onError, }); function NodeRendererComponent(props: NodeRendererProps) { const { nodesDraggable, nodesConnectable, nodesFocusable, elementsSelectable, onError } = useStore(selector); const nodeIds = useVisibleNodeIds(() => props.onlyRenderVisibleElements); const resizeObserver = useResizeObserver(); return (
{(nodeId) => { return ( // The split of responsibilities between NodeRenderer and // NodeComponentWrapper may appear weird. However, it’s designed to // minimize the cost of updates when individual nodes change. // // For example, when you’re dragging a single node, that node gets // updated multiple times per second. If `NodeRenderer` were to update // every time, it would have to re-run the `nodes.map()` loop every // time. This gets pricey with hundreds of nodes, especially if every // loop cycle does more than just rendering a JSX element! // // As a result of this choice, we took the following implementation // decisions: // - NodeRenderer subscribes *only* to node IDs – and therefore // rerender *only* when visible nodes are added or removed. // - NodeRenderer performs all operations the result of which can be // shared between nodes (such as creating the `ResizeObserver` // instance, or subscribing to `selector`). This means extra prop // drilling into `NodeComponentWrapper`, but it means we need to run // these operations only once – instead of once per node. // - Any operations that you’d normally write inside `nodes.map` are // moved into `NodeComponentWrapper`. This ensures they are // memorized – so if `NodeRenderer` *has* to rerender, it only // needs to regenerate the list of nodes, nothing else. // key={nodeId} id={nodeId} nodeTypes={props.nodeTypes} nodeExtent={props.nodeExtent} nodeOrigin={props.nodeOrigin} onClick={props.onNodeClick} onMouseEnter={props.onNodeMouseEnter} onMouseMove={props.onNodeMouseMove} onMouseLeave={props.onNodeMouseLeave} onContextMenu={props.onNodeContextMenu} onDoubleClick={props.onNodeDoubleClick} noDragClassName={props.noDragClassName} noPanClassName={props.noPanClassName} rfId={props.rfId} disableKeyboardA11y={props.disableKeyboardA11y} resizeObserver={resizeObserver()} nodesDraggable={nodesDraggable.get()} nodesConnectable={nodesConnectable.get()} nodesFocusable={nodesFocusable.get()} elementsSelectable={elementsSelectable.get()} onError={onError.get()} /> ); }}
); } NodeRendererComponent.displayName = 'NodeRenderer'; export const NodeRenderer = NodeRendererComponent;