# React Flow as the Single Source of Truth: How nodeterm Manages Node State

> Discover how nodeterm uses React Flow as the single source of truth for all node state. Learn how React Flow manages persistence and auxiliary services for efficient state management.

- Repository: [eneskirca/nodeterm](https://github.com/eneskirca/nodeterm)
- Tags: architecture
- Published: 2026-08-26

---

**Yes, React Flow serves as the exclusive, live source of truth for all canvas node state in nodeterm, with all persistence layers and auxiliary services reading from or writing to the React Flow array rather than maintaining parallel copies.**

In the **nodeterm** codebase, architectural decisions explicitly designate React Flow as the definitive in-memory model for node positions, sizes, and relationships. Unlike applications that synchronize state between a global store and a rendering layer, nodeterm treats the React Flow component itself as the authoritative data layer, ensuring consistency across peer sync, UI interactions, and disk persistence.

## How nodeterm Establishes React Flow as the Authoritative State

The application architecture centralizes all live node representation within the React Flow component. According to the source code in [`src/shared/canvas-publish.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/canvas-publish.ts), the renderer’s canvas maintains nodes in a React Flow array described explicitly as **"the single live source of truth"**【src/shared/canvas-publish.ts†L3-L10】.

### State Initialization and Hydration

When a project activates, the system does not maintain dual state copies. Instead, persisted node lists undergo conversion directly into live React Flow nodes. In [`src/renderer/state/workspace.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/state/workspace.ts), the loading logic ensures parent nodes appear before children to satisfy React Flow’s rendering requirements【src/renderer/state/workspace.ts†L1622-L1624】.

This conversion process means the React Flow array becomes the immediate, mutable representation of the canvas. No intermediate global store holds a parallel copy; the `useNodesState` hook manages the definitive collection.

### Mutation Flow and Direct Updates

All mutations—whether triggered by user actions, peer synchronization payloads, or background services—apply **directly to the live React Flow node array**. Code at lines 1776-1789 of [`src/renderer/state/workspace.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/state/workspace.ts) demonstrates that peer mutations transform the existing React Flow nodes in place rather than updating a separate state layer【src/renderer/state/workspace.ts†L1776-L1789】.

## Persistence Guards and Data Integrity

To prevent divergent state from reaching disk, nodeterm implements validation logic that confirms the React Flow instance holds the authoritative nodes before committing data.

### Validating State Before Persistence

The **persist-guard** logic in [`src/renderer/state/persistGuards.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/state/persistGuards.ts) checks whether the current React Flow instance contains the nodes for the active project. This guard ensures that only the live React Flow state serializes to storage【src/renderer/state/persistGuards.ts†L6-L14】.

Auxiliary data structures, such as the context-link map, derive their content from the live React Flow edges rather than maintaining independent edge state. The `context-link` map in [`src/shared/context-link-map.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/context-link-map.ts) builds its relationships by reading the active edge array directly from React Flow【src/shared/context-link-map.ts†L4-L8】.

## Practical Implementation: Working with the Single Source

The following patterns demonstrate how nodeterm components interact with the authoritative React Flow state:

```typescript
// Initialise the live node state – React Flow is the source of truth
const [nodes, setNodes] = useNodesState([]);

// Convert persisted project data into live React Flow nodes
function loadProject(project) {
  const flowNodes = nodeStatesToFlow(project.nodeStates); // respects parent-first order
  setNodes(flowNodes);
}

// Apply a peer mutation directly to the live React Flow array
function applyPeerMutation(mutation) {
  const newNodes = applyMutationToFlow(nodes, mutation);
  setNodes(newNodes); // mutation updates the single source
}

// Persist the current canvas only if it matches the active project
if (activeLiveNodeIds.has(node.id)) {
  saveProject({ nodeStates: flowToNodeStates(nodes) });
}

```

This implementation ensures that `nodes` remains the **single source of truth** throughout the application lifecycle, from initial hydration through real-time collaboration to final persistence.

## Summary

- **React Flow owns the live state**: The canvas array in React Flow constitutes the exclusive in-memory representation of node positions and relationships, as declared in [`src/shared/canvas-publish.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/canvas-publish.ts).
- **Direct mutation pattern**: All updates—UI actions, peer sync, or programmatic changes—apply immediately to the React Flow node array via `setNodes`, with no parallel state copies in auxiliary stores.
- **Parent-first hydration**: Loading projects converts persisted data directly into React Flow nodes, ensuring proper rendering order by placing parent nodes before children during initialization.
- **Guarded persistence**: The system validates that React Flow contains the active project nodes before writing to disk, preventing stale or divergent state from reaching storage.
- **Derived auxiliary data**: Maps and link structures build themselves from the live React Flow edges rather than maintaining separate edge state, ensuring consistency across the application.

## Frequently Asked Questions

### Does nodeterm use a global state manager like Redux for node data?

No. According to the source code analysis, nodeterm explicitly avoids maintaining parallel copies of node state in external stores. The React Flow component managed through `useNodesState` serves as the sole state container, with persistence guards in [`src/renderer/state/persistGuards.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/state/persistGuards.ts) ensuring disk writes originate exclusively from the live React Flow array.

### How does nodeterm handle real-time collaboration without conflicting state sources?

Peer mutations apply directly to the live React Flow node array in [`src/renderer/state/workspace.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/state/workspace.ts) (lines 1776-1789). When a sync payload arrives, the system transforms the current React Flow nodes immediately rather than merging with a separate state layer, ensuring all collaborators see the same single source of truth.

### What prevents nodeterm from saving stale node data to disk?

The **persist-guard** logic validates that the current React Flow instance holds the nodes for the active project before committing to storage. As implemented in [`src/renderer/state/persistGuards.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/state/persistGuards.ts) (lines 6-14), this check guarantees that only the live, authoritative React Flow state serializes to the persisted project file.

### Are edge relationships also managed by React Flow?

Yes. Auxiliary structures like the context-link map derive their data from the live React Flow edges. The code in [`src/shared/context-link-map.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/context-link-map.ts) (lines 4-8) demonstrates that edge relationships are read directly from the React Flow array, not from a separate edge store, maintaining the single source of truth pattern for connections as well as nodes.