Computing Flows
This guide assumes you’re familiar with custom nodes and the core concepts.
Usually you’d handle your data outside of Vue Flow — on a server or in a store. But you can also compute data directly inside the graph, updating a node from the data of the nodes connected to it. This guide builds a small app that mixes a color from three number inputs and decides whether that color is light or dark.
What we’re going to build
Three number inputs (red, green, blue) feed a color node, which feeds a “lightness” node that branches to a log node depending on whether the color is light or dark. Change the inputs and watch the values flow through:
Reading and writing node data
Two composables do the work:
useNodeConnectionstells a node which other nodes are connected to a given handle.useNodesDatareads thedataof those connected nodes.
And you write to a node’s own data with
updateNodeData from useVueFlow().
The input node
NumberInput keeps its value in local state (writing straight into node data can make the cursor
jump), caps it to 0–255, and mirrors it into the node’s data on every change:
<script setup lang="ts">
import type { NodeProps } from '@xyflow/vue';
import { Handle, Position, useVueFlow } from '@xyflow/vue';
import { ref } from 'vue';
const props = defineProps<NodeProps>();
const { updateNodeData } = useVueFlow();
const number = ref(Number(props.data.value ?? 0));
function onInput(event: Event) {
const capped = Math.round(Math.min(255, Math.max(0, Number((event.target as HTMLInputElement).value))));
number.value = capped;
updateNodeData(props.id, { value: capped });
}
</script>
<template>
<label :for="`number-${id}`">{{ data.label }}</label>
<input :id="`number-${id}`" class="nodrag" type="number" min="0" max="255" :value="number" @input="onInput">
<Handle type="source" :position="Position.Right" />
</template>By default updateNodeData merges into the existing data. Pass { replace: true } as a second
option to overwrite it instead.
Reading connected data
The color node has one target handle per channel. Reading a channel — find the connection, then read the connected node’s data — is a natural composable, so extract it into its own file:
import type { NumberNode } from './types';
import { useNodeConnections, useNodesData } from '@xyflow/vue';
import { computed } from 'vue';
export function useChannel(handleId: string) {
const connections = useNodeConnections({ handleType: 'target', handleId });
const nodeData = useNodesData<NumberNode>(() => connections.value[0]?.source ?? '');
return computed(() => nodeData.value?.data.value ?? 0);
}The color node calls it once per channel, computes the color, and writes it back to its own data so the next node can read it:
<script setup lang="ts">
import type { NodeProps } from '@xyflow/vue';
import { Handle, Position, useVueFlow } from '@xyflow/vue';
import { computed, watch } from 'vue';
import { useChannel } from './useChannel';
const props = defineProps<NodeProps>();
const { updateNodeData } = useVueFlow();
const r = useChannel('red');
const g = useChannel('green');
const b = useChannel('blue');
const color = computed(() => ({ r: r.value, g: g.value, b: b.value }));
// expose the computed color so downstream nodes can read it
watch(color, value => updateNodeData(props.id, { color: value }), { immediate: true });
</script>
<template>
<Handle id="red" type="target" :position="Position.Left" :style="{ top: '25%' }" />
<Handle id="green" type="target" :position="Position.Left" :style="{ top: '50%' }" />
<Handle id="blue" type="target" :position="Position.Left" :style="{ top: '75%' }" />
<div :style="{ background: `rgb(${color.r}, ${color.g}, ${color.b})` }" />
<Handle type="source" :position="Position.Right" />
</template>When a node has multiple handles of the same type, give each one an id and pass it to
useNodeConnections so you read the right connection.
Transforming and branching
Because a node’s data is reactive, you can chain transforms. The Lightness node reads the
color, computes its relative
luminance , and splits the result across two
source handles — writing the color to light or dark and setting the other to null:
const luminance = computed(() => 0.2126 * color.value.r + 0.7152 * color.value.g + 0.0722 * color.value.b);
const isLight = computed(() => luminance.value >= 128);
watch([color, isLight], () => {
updateNodeData(props.id, {
light: isLight.value ? color.value : null,
dark: isLight.value ? null : color.value,
});
}, { immediate: true });A downstream node then reads only the handle it’s connected to, through the connection’s
sourceHandle. Treating a null value as “no data” is a simple way to keep one branch active at
a time:
const connections = useNodeConnections({ handleType: 'target' });
const sourceData = useNodesData<LightnessNode>(() => connections.value[0]?.source ?? '');
const sourceHandle = computed(() => connections.value[0]?.sourceHandle ?? '');
const value = computed(() => sourceData.value?.data?.[sourceHandle.value as 'light' | 'dark'] ?? null);Summary
To compute data through a flow:
- store values on a node with
updateNodeData, - find connected nodes with
useNodeConnections, and - read their data with
useNodesData.
Chain those together to transform data along the way, and branch by treating missing data as a “stop”. Pick one consistent shape for your node data across the whole graph and it stays easy to reason about.