This document describes all available React hooks for working with @gravity-ui/graph.
- useGraph - Creates and manages the graph instance
- useLayer - Adds and manages graph layers with automatic cleanup
- useGraphEvent - Subscribes to a single graph event with optional debouncing
- useGraphEvents - Subscribes to multiple graph events at once
- useBlockState - Gets and subscribes to block state changes
- useBlockViewState - Gets the view component of a block
- useBlockAnchorState - Gets and subscribes to anchor state changes
- useSignal - Subscribes to signal values for reactive updates
- useComputedSignal - Creates computed signals from other signals
- useSignalEffect - Runs side effects when signal values change
- useSchedulerDebounce - Creates frame-synchronized debounced function
- useSchedulerThrottle - Creates frame-synchronized throttled function
- useScheduledTask - Schedules task for frame-based execution
- useSceneChange - Reacts to scene updates (camera changes, viewport updates)
- Combining Hooks - Examples of using multiple hooks together
- Performance Optimization - Debouncing frequent events
import {
useGraph,
useGraphEvent,
useGraphEvents,
useLayer,
useBlockState,
useBlockViewState,
useBlockAnchorState,
useSignal,
useComputedSignal,
useSignalEffect,
useSchedulerDebounce,
useSchedulerThrottle,
useScheduledTask,
useSceneChange,
} from "@gravity-ui/graph/react";The main hook for creating and managing a Graph instance.
import { useGraph, type HookGraphParams } from "@gravity-ui/graph/react";
import type { Graph, TBlock, TConnection } from "@gravity-ui/graph";
const config: HookGraphParams = {
name: "my-graph",
settings: {
canDragCamera: true,
canZoomCamera: true,
},
viewConfiguration: {
colors: {
block: {
background: "rgba(37, 27, 37, 1)",
},
},
},
};
function MyGraph(): JSX.Element {
const {
graph, // Graph - graph instance
api, // PublicGraphApi - public API for graph manipulation
setSettings, // (settings: TGraphSettingsConfig) => void
setViewConfiguration, // (config: HookGraphParams["viewConfiguration"]) => void
setEntities, // <B extends TBlock, C extends TConnection>(entities: { blocks?: B[]; connections?: C[] }) => void
updateEntities, // <B extends TBlock, C extends TConnection>(entities: { blocks?: B[]; connections?: C[] }) => void
addLayer, // <T extends Constructor<Layer>>(layerCtor: T, props: LayerPublicProps<T>) => InstanceType<T>
zoomTo, // (target: TGraphZoomTarget, config?: ZoomConfig) => void
start, // () => void
stop, // () => void
} = useGraph(config);
React.useEffect(() => {
setEntities({
blocks: [...],
connections: [...],
});
start();
}, []);
return <GraphCanvas graph={graph} />;
}| Parameter | Type | Description |
|---|---|---|
graph |
Graph |
Optional existing Graph instance to use |
name |
string |
Configuration name for the graph |
settings |
TGraphSettingsConfig |
Graph behavior settings |
viewConfiguration |
{ colors?: RecursivePartial<TGraphColors>; constants?: RecursivePartial<TGraphConstants> } |
Visual configuration |
layers |
LayerConfig[] |
Initial layers to add to the graph |
| Property | Type | Description |
|---|---|---|
graph |
Graph |
The Graph instance |
api |
PublicGraphApi |
Public API for graph manipulation |
setSettings |
(settings: TGraphSettingsConfig) => void |
Update graph settings |
setViewConfiguration |
(config: HookGraphParams["viewConfiguration"]) => void |
Update view configuration |
setEntities |
<B extends TBlock, C extends TConnection>(entities: { blocks?: B[]; connections?: C[] }) => void |
Replace all entities |
updateEntities |
<B extends TBlock, C extends TConnection>(entities: { blocks?: B[]; connections?: C[] }) => void |
Merge with existing entities |
addLayer |
<T extends Constructor<Layer>>(layerCtor: T, props: LayerPublicProps<T>) => InstanceType<T> |
Add a new layer |
zoomTo |
(target: TGraphZoomTarget, config?: ZoomConfig) => void |
Zoom to target |
start |
() => void |
Start the graph |
stop |
() => void |
Stop the graph |
Hook for managing graph layers. Automatically handles layer initialization, props updates, and cleanup.
import { useLayer } from "@gravity-ui/graph/react";
import { DevToolsLayer, type DevToolsLayerProps } from "@gravity-ui/graph/plugins";
import type { Graph } from "@gravity-ui/graph";
function MyGraph(): JSX.Element {
const { graph } = useGraph({});
// Layer is automatically added and cleaned up
// Returns: DevToolsLayer | null
const devToolsLayer: DevToolsLayer | null = useLayer(graph, DevToolsLayer, {
showRuler: true,
rulerSize: 20,
});
// Props updates are handled automatically
const [rulerSize, setRulerSize] = useState<number>(20);
const devToolsLayerWithState: DevToolsLayer | null = useLayer(graph, DevToolsLayer, {
showRuler: true,
rulerSize, // When this changes, layer will be updated
});
return <GraphCanvas graph={graph} />;
}| Parameter | Type | Description |
|---|---|---|
graph |
Graph | null |
Graph instance |
layerCtor |
T extends Constructor<Layer> |
Layer class constructor |
props |
LayerPublicProps<T> |
Layer properties (excluding internal props like root, camera, graph, emitter) |
Returns InstanceType<T> | null - the layer instance or null if graph is not initialized.
Hook for subscribing to a single graph event with optional debouncing.
import { useGraphEvent } from "@gravity-ui/graph/react";
import type { Graph, ESchedulerPriority } from "@gravity-ui/graph";
import type { UnwrapGraphEventsDetail, UnwrapGraphEvents } from "@gravity-ui/graph";
interface Props {
graph: Graph;
}
function MyComponent({ graph }: Props): JSX.Element | null {
// Basic usage - callback receives (detail, event)
useGraphEvent(
graph,
"block-change",
(detail: UnwrapGraphEventsDetail<"block-change">, event: UnwrapGraphEvents<"block-change">) => {
console.log("Block changed:", detail.block);
}
);
// With debounce options
useGraphEvent(
graph,
"camera-change",
(detail: UnwrapGraphEventsDetail<"camera-change">) => {
console.log("Camera:", detail.camera);
},
{
priority: ESchedulerPriority.MEDIUM,
frameInterval: 2, // Wait 2 frames
frameTimeout: 100, // Wait at least 100ms
}
);
// Prevent default behavior
useGraphEvent(
graph,
"connection-created",
(detail: UnwrapGraphEventsDetail<"connection-created">, event: UnwrapGraphEvents<"connection-created">) => {
event.preventDefault();
// Custom connection logic
}
);
return null;
}| Parameter | Type | Description |
|---|---|---|
graph |
Graph | null |
Graph instance |
event |
Event extends keyof GraphEventsDefinitions |
Event name to subscribe to |
callback |
(data: UnwrapGraphEventsDetail<Event>, event: UnwrapGraphEvents<Event>) => void |
Event handler |
debounceParams |
{ priority?: ESchedulerPriority; frameInterval?: number; frameTimeout?: number } |
Optional debounce configuration |
| Option | Type | Default | Description |
|---|---|---|---|
priority |
ESchedulerPriority |
MEDIUM |
Scheduler priority |
frameInterval |
number |
1 |
Frames to wait before execution |
frameTimeout |
number |
0 |
Minimum time in ms to wait |
Hook for subscribing to multiple graph events at once using callback props style.
import { useGraphEvents } from "@gravity-ui/graph/react";
import type { Graph } from "@gravity-ui/graph";
import type { TGraphEventCallbacks } from "@gravity-ui/graph/react";
interface Props {
graph: Graph;
}
function MyComponent({ graph }: Props): JSX.Element | null {
const eventHandlers: Partial<TGraphEventCallbacks> = {
onBlockChange: ({ block }) => {
console.log("Block changed:", block);
},
onConnectionCreated: (detail, event) => {
console.log("Connection created:", detail);
},
onBlocksSelectionChange: ({ changes }) => {
console.log("Selection added:", changes.add);
console.log("Selection removed:", changes.removed);
},
};
useGraphEvents(graph, eventHandlers);
return null;
}| Parameter | Type | Description |
|---|---|---|
graph |
Graph | null |
Graph instance |
events |
Partial<TGraphEventCallbacks> |
Object with event callbacks |
Hook to get and subscribe to block state changes.
import { useBlockState } from "@gravity-ui/graph/react";
import type { Graph, TBlock, TBlockId } from "@gravity-ui/graph";
import type { BlockState } from "@gravity-ui/graph";
interface Props {
graph: Graph;
blockId: TBlockId;
}
function BlockInfo({ graph, blockId }: Props): JSX.Element | null {
// Can pass block object or just the ID
// Returns: BlockState | undefined
const blockState: BlockState | undefined = useBlockState(graph, blockId);
if (!blockState) return null;
const geometry = blockState.$geometry.value;
return (
<div>
<p>Position: {geometry.x}, {geometry.y}</p>
<p>Size: {geometry.width} x {geometry.height}</p>
<p>Selected: {blockState.selected ? "Yes" : "No"}</p>
</div>
);
}| Parameter | Type | Description |
|---|---|---|
graph |
Graph |
Graph instance |
block |
TBlock | TBlockId |
Block object or block ID |
Returns BlockState | undefined - the block state object that updates reactively.
Hook to get the view component of a block. Useful for accessing rendering-specific state.
import { useBlockViewState } from "@gravity-ui/graph/react";
import type { Graph, TBlockId } from "@gravity-ui/graph";
import type { Block } from "@gravity-ui/graph";
interface Props {
graph: Graph;
blockId: TBlockId;
}
function BlockView({ graph, blockId }: Props): JSX.Element | null {
// Returns: Block | undefined (the view component)
const viewComponent: Block | undefined = useBlockViewState(graph, blockId);
if (!viewComponent) return null;
// Access view-specific methods
const anchors = viewComponent.getAnchors();
return <div>Block has {anchors.length} anchors</div>;
}| Parameter | Type | Description |
|---|---|---|
graph |
Graph |
Graph instance |
block |
TBlock | TBlockId |
Block object or block ID |
Returns the block's view component (Block) or undefined.
Hook to get and subscribe to anchor state changes.
import { useBlockAnchorState } from "@gravity-ui/graph/react";
import type { Graph } from "@gravity-ui/graph";
import type { TAnchor, AnchorState } from "@gravity-ui/graph";
interface Props {
graph: Graph;
anchor: TAnchor;
}
function AnchorInfo({ graph, anchor }: Props): JSX.Element | null {
// Returns: AnchorState | undefined
const anchorState: AnchorState | undefined = useBlockAnchorState(graph, anchor);
if (!anchorState) return null;
return (
<div className={anchorState.selected ? "selected" : ""}>
Anchor: {anchor.id}
</div>
);
}| Parameter | Type | Description |
|---|---|---|
graph |
Graph |
Graph instance |
anchor |
TAnchor |
Anchor object (must include blockId) |
Returns AnchorState | undefined - the anchor state object.
These hooks provide integration with @preact/signals-core for reactive state management.
Hook to subscribe to a signal and get the current value. Re-renders component when signal value changes.
import { useSignal } from "@gravity-ui/graph/react";
import type { Signal } from "@preact/signals-core";
import type { BlockState, TBlockGeometry } from "@gravity-ui/graph";
interface Props {
blockState: BlockState;
}
function BlockGeometry({ blockState }: Props): JSX.Element {
// blockState.$geometry is Signal<TBlockGeometry>
// useSignal returns: TBlockGeometry
const geometry: TBlockGeometry = useSignal(blockState.$geometry);
return (
<div>
Position: ({geometry.x}, {geometry.y})
Size: {geometry.width} x {geometry.height}
</div>
);
}| Parameter | Type | Description |
|---|---|---|
signal |
Signal<T> |
Signal to subscribe to |
Returns T - the current value of the signal.
Hook to create and subscribe to a computed signal. Useful for derived state.
import { useComputedSignal } from "@gravity-ui/graph/react";
import type { DependencyList } from "react";
import type { BlockState } from "@gravity-ui/graph";
interface Point {
x: number;
y: number;
}
interface Props {
blockState: BlockState;
}
function BlockCenter({ blockState }: Props): JSX.Element {
// Compute center position from geometry
// Returns: Point (the computed value)
const center: Point = useComputedSignal(
(): Point => {
const geo = blockState.$geometry.value;
return {
x: geo.x + geo.width / 2,
y: geo.y + geo.height / 2,
};
},
[blockState] as DependencyList
);
return <div>Center: ({center.x}, {center.y})</div>;
}| Parameter | Type | Description |
|---|---|---|
compute |
() => T |
Computation function that reads signals |
deps |
DependencyList |
Dependencies array (like useEffect) |
Returns T - the computed value, updated when dependent signals change.
Hook to run side effects when signal values change. Similar to useEffect but for signals.
import { useSignalEffect } from "@gravity-ui/graph/react";
import type { DependencyList } from "react";
import type { BlockState } from "@gravity-ui/graph";
interface Props {
blockState: BlockState;
}
function BlockLogger({ blockState }: Props): null {
useSignalEffect(
(): void => {
// This runs whenever block geometry changes
console.log("Block moved to:", blockState.$geometry.value);
},
[blockState] as DependencyList
);
return null;
}| Parameter | Type | Description |
|---|---|---|
effectFn |
() => void |
Effect function that reads signals |
deps |
DependencyList |
Dependencies array (like useEffect) |
These hooks integrate with the graph's internal scheduler for frame-based timing control. They are designed primarily for synchronizing UI updates with graph component changes on a per-frame basis.
See also: Scheduler System Documentation - Detailed information about the scheduler architecture, priority levels, and task queue management.
Use scheduler hooks when:
- You need to react to frequent graph events (block dragging, camera movement)
- You want to prepare state for the next render frame
- You need precise frame-based timing synchronized with graph rendering
Use regular debounce/throttle when:
- You need simple debouncing for user input (search, form validation)
- The operation is not related to graph rendering
- You don't need frame-level synchronization
Scheduler hooks support priority levels to control execution order within each frame:
enum ESchedulerPriority {
HIGHEST = 0, // Critical updates, executed first
HIGH = 1, // Important updates
MEDIUM = 2, // Default priority
LOW = 3, // Less critical updates
LOWEST = 4, // Background tasks, executed last
}Lower numeric values execute first in each frame. See Priority Levels and Task Queue for more details.
When reacting to frequent events like block movement, a naive setTimeout-based debounce creates many setTimeout/clearTimeout operations per second. This overhead reduces FPS and creates timing inconsistencies with graph rendering.
Scheduler hooks solve this by:
- Integrating with the graph's render loop
- Batching operations to specific frames
- Respecting priority levels to avoid blocking critical updates
// ❌ Bad: Many setTimeout/clearTimeout calls during drag
const naiveDebounce = useMemo(() => {
let timeoutId: number;
return (fn: () => void) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(fn, 16); // ~60fps
};
}, []);
// ✅ Good: Synchronized with graph render frames
const scheduledUpdate = useSchedulerDebounce(
() => updateSidebar(),
{ frameInterval: 1 }
);Note: Scheduler hooks use the graph's internal scheduler resources. For operations unrelated to graph rendering, prefer standard debounce utilities to avoid consuming graph performance budget.
Hook to create a debounced function that delays execution until both frame and time conditions are met.
The function will only execute when BOTH conditions are satisfied:
- At least
frameIntervalframes have passed since the last invocation - At least
frameTimeoutmilliseconds have passed since the last invocation
import { useSchedulerDebounce, useGraphEvent } from "@gravity-ui/graph/react";
import type { ESchedulerPriority, Graph, TBlock } from "@gravity-ui/graph";
interface DebouncedFn<T extends (...args: unknown[]) => void> {
(...args: Parameters<T>): void;
cancel: () => void;
}
// Example: Update sidebar info when block is being dragged
function BlockInfoSidebar({ graph }: { graph: Graph }): JSX.Element {
const [blockPosition, setBlockPosition] = useState<{ x: number; y: number } | null>(null);
// Debounced update synchronized with graph frames
// This avoids creating hundreds of setTimeout calls during drag
const updatePosition: DebouncedFn<(x: number, y: number) => void> = useSchedulerDebounce(
(x: number, y: number): void => {
setBlockPosition({ x, y });
},
{
priority: ESchedulerPriority.LOW, // Lower priority than graph rendering
frameInterval: 1, // Update at most once per frame
frameTimeout: 0, // No additional time delay
}
);
// React to block changes during drag
useGraphEvent(graph, "block-change", ({ block }) => {
updatePosition(block.x, block.y);
});
// Cleanup is handled automatically on unmount
// Or cancel manually: updatePosition.cancel();
return (
<div className="sidebar">
{blockPosition && (
<p>Position: ({blockPosition.x}, {blockPosition.y})</p>
)}
</div>
);
}| Parameter | Type | Description |
|---|---|---|
fn |
T extends (...args: unknown[]) => void |
Function to debounce |
options |
TDebounceOptions |
Configuration options |
| Option | Type | Default | Description |
|---|---|---|---|
priority |
ESchedulerPriority |
MEDIUM |
Scheduler priority |
frameInterval |
number |
1 |
Frames to wait before execution |
frameTimeout |
number |
0 |
Minimum time in ms to wait |
Returns a debounced function with cancel() method to abort pending executions.
Hook to create a throttled function that limits execution frequency.
Unlike debounce, throttle executes immediately on the first call and then enforces the delay for subsequent calls.
import { useSchedulerThrottle, useGraphEvent } from "@gravity-ui/graph/react";
import type { ESchedulerPriority, Graph, TCameraState } from "@gravity-ui/graph";
interface ThrottledFn<T extends (...args: unknown[]) => void> {
(...args: Parameters<T>): void;
cancel: () => void;
}
// Example: Update minimap during camera pan/zoom
function MinimapOverlay({ graph }: { graph: Graph }): JSX.Element {
const [viewport, setViewport] = useState<TCameraState | null>(null);
// Throttle camera updates - executes immediately on first call,
// then waits for frame interval before next execution
const throttledCameraUpdate: ThrottledFn<(camera: TCameraState) => void> = useSchedulerThrottle(
(camera: TCameraState): void => {
setViewport(camera);
},
{
priority: ESchedulerPriority.LOW,
frameInterval: 2, // Update at most every 2 frames
frameTimeout: 32, // At most once per ~32ms
}
);
// Camera events fire very frequently during pan/zoom
useGraphEvent(graph, "camera-change", ({ camera }) => {
throttledCameraUpdate(camera);
});
return (
<div className="minimap">
{viewport && (
<div
className="viewport-indicator"
style={{
transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.scale})`,
}}
/>
)}
</div>
);
}| Parameter | Type | Description |
|---|---|---|
fn |
T extends (...args: unknown[]) => void |
Function to throttle |
options |
TDebounceOptions |
Configuration options |
| Option | Type | Default | Description |
|---|---|---|---|
priority |
ESchedulerPriority |
MEDIUM |
Scheduler priority |
frameInterval |
number |
1 |
Frames to wait between executions |
frameTimeout |
number |
0 |
Minimum time in ms between executions |
Returns a throttled function with cancel() method to abort pending executions.
Hook to schedule a task for execution after a certain number of frames have passed.
The scheduled task will execute once the specified frame interval has elapsed. The task is automatically cancelled when the component unmounts.
import { useScheduledTask, useBlockState, useSignal } from "@gravity-ui/graph/react";
import type { ESchedulerPriority, Graph, TBlockId } from "@gravity-ui/graph";
// Example: Prepare derived state for the next render frame
function BlockMetrics({ graph, blockId }: { graph: Graph; blockId: TBlockId }): JSX.Element {
const blockState = useBlockState(graph, blockId);
const geometry = blockState ? useSignal(blockState.$geometry) : null;
const [metrics, setMetrics] = useState<{ area: number; center: { x: number; y: number } } | null>(null);
// Schedule metrics calculation for next frame
// This ensures we don't calculate during the render phase
useScheduledTask(
(): void => {
if (geometry) {
setMetrics({
area: geometry.width * geometry.height,
center: {
x: geometry.x + geometry.width / 2,
y: geometry.y + geometry.height / 2,
},
});
}
},
{
priority: ESchedulerPriority.LOW, // Run after graph updates
frameInterval: 1, // Execute on next frame
}
);
return (
<div className="block-metrics">
{metrics && (
<>
<p>Area: {metrics.area}px²</p>
<p>Center: ({metrics.center.x}, {metrics.center.y})</p>
</>
)}
</div>
);
}| Parameter | Type | Description |
|---|---|---|
fn |
T extends (...args: unknown[]) => void |
Function to schedule |
options |
Omit<TScheduleOptions, "once"> |
Configuration options |
| Option | Type | Default | Description |
|---|---|---|---|
priority |
ESchedulerPriority |
MEDIUM |
Scheduler priority |
frameInterval |
number |
1 |
Frames to wait before execution |
Hook to react to scene updates. The scene is considered updated when the camera changes or when components change their position in the viewport.
This hook is useful for updating UI elements that depend on the visible area of the graph, such as:
- Minimaps showing the current viewport
- Visible block counters
- Custom overlays that need to update when the view changes
The hook automatically:
- Subscribes to camera changes
- Subscribes to hitTest updates (when blocks enter/leave viewport)
- Handles initial state on mount
- Cleans up subscriptions on unmount
import { useSceneChange } from "@gravity-ui/graph/react";
import type { Graph, TRect } from "@gravity-ui/graph";
// Example: Update usable rect indicator when scene changes
interface Props {
graph: Graph;
}
function UsableRectOverlay({ graph }: Props): JSX.Element {
const [usableRect, setUsableRect] = useState<TRect | null>(null);
// React to scene changes with frame-synchronized updates
useSceneChange(graph, (): void => {
// Get current usable rect (bounding box of all graph elements)
const rect = graph.hitTest.getUsableRect();
setUsableRect(rect);
});
if (!usableRect) return null;
return (
<div className="usable-rect-info">
<p>Graph bounds:</p>
<p>X: {usableRect.x.toFixed(0)}, Y: {usableRect.y.toFixed(0)}</p>
<p>Size: {usableRect.width.toFixed(0)} × {usableRect.height.toFixed(0)}</p>
</div>
);
}
// Example: Update minimap when viewport changes
function GraphMinimap({ graph }: Props): JSX.Element {
const [cameraState, setCameraState] = useState(graph.camera.getCameraState());
useSceneChange(graph, (): void => {
// Get current camera state
const state = graph.camera.getCameraState();
setCameraState(state);
});
return (
<div className="minimap">
<div className="minimap-viewport">
<p>Scale: {(cameraState.scale * 100).toFixed(0)}%</p>
<p>Position: ({cameraState.x.toFixed(0)}, {cameraState.y.toFixed(0)})</p>
</div>
</div>
);
}
// Example: Track visible viewport rectangle
function ViewportIndicator({ graph }: Props): JSX.Element {
const [viewport, setViewport] = useState(graph.camera.getVisibleCameraRect());
useSceneChange(graph, (): void => {
// Get visible camera rect (respects viewport insets)
const rect = graph.camera.getVisibleCameraRect();
setViewport(rect);
});
return (
<div className="viewport-info">
<p>Visible area:</p>
<p>{viewport.width.toFixed(0)} × {viewport.height.toFixed(0)}</p>
</div>
);
}| Parameter | Type | Description |
|---|---|---|
graph |
Graph |
Graph instance |
fn |
() => void |
Function to call when scene updates |
The hook uses useSchedulerDebounce with HIGHEST priority and frameInterval: 1 to ensure scene updates are processed efficiently:
const handleCameraChange = useSchedulerDebounce(fn, {
priority: ESchedulerPriority.HIGHEST,
frameInterval: 1,
});This means:
- Updates are synchronized with the graph's render loop
- Multiple scene changes within a frame are batched into one update
- The callback executes at most once per frame
- High priority ensures scene updates happen before other scheduled tasks
The hook subscribes to:
camera-changeevent - Fires when camera position, scale, or rotation changes- HitTest
updateevent - Fires when blocks enter or leave the viewport - Initial mount - Calls the function immediately on component mount
✅ Good use cases:
- Updating minimaps or viewport indicators
- Counting visible blocks
- Updating overlays based on viewport
- Recalculating layout-dependent UI elements
❌ Not recommended for:
- Updating individual block styles (use
useBlockStateinstead) - Heavy computations on every frame (consider additional debouncing)
- Operations that don't depend on viewport changes
import {
useGraph,
useGraphEvent,
useBlockState,
useSignal,
GraphCanvas,
} from "@gravity-ui/graph/react";
import type { TBlockId, TBlockGeometry, BlockState } from "@gravity-ui/graph";
function MyGraph(): JSX.Element {
const { graph, setEntities, start } = useGraph({
settings: { canDragBlocks: true },
});
// Track selected block
const [selectedBlockId, setSelectedBlockId] = useState<TBlockId | null>(null);
useGraphEvent(graph, "blocks-selection-change", ({ changes }) => {
if (changes.add.length > 0) {
setSelectedBlockId(changes.add[0]);
}
});
// Get selected block state
const selectedBlock = useBlockState(graph, selectedBlockId);
// Subscribe to geometry changes (conditional hook usage - be careful!)
const geometry: TBlockGeometry | null = useSignal(selectedBlock.$geometry)
return (
<div>
<GraphCanvas graph={graph} />
{geometry && (
<div className="info-panel">
Selected: {selectedBlockId}
Position: ({geometry.x}, {geometry.y})
</div>
)}
</div>
);
}import { useGraphEvent } from "@gravity-ui/graph/react";
import { ESchedulerPriority } from "@gravity-ui/graph";
import type { Graph, TCameraState } from "@gravity-ui/graph";
interface Props {
graph: Graph;
}
function CameraInfo({ graph }: Props): JSX.Element | null {
const [cameraState, setCameraState] = useState<TCameraState | null>(null);
// Debounce camera updates for performance
useGraphEvent(
graph,
"camera-change",
({ camera }: { camera: TCameraState }): void => {
setCameraState(camera);
},
{
priority: ESchedulerPriority.LOW,
frameInterval: 2,
frameTimeout: 50,
}
);
if (!cameraState) return null;
return (
<div>
Zoom: {cameraState.scale.toFixed(2)}
Pan: ({cameraState.x.toFixed(0)}, {cameraState.y.toFixed(0)})
</div>
);
}