In the previous chapter, Ink DOM & Layout Engine, we discovered that Ink builds an invisible tree of nodes (the Ink DOM) and uses the Yoga engine to calculate layout.
But there is a missing link.
When you write React code, you are dealing with State (useState) and Virtual Components. You aren't manually creating ink-box nodes or calculating math.
Who connects your React state to the Ink DOM? That is the job of the React Reconciler.
Imagine React as an Architect.
Ink's DOM is the Construction Site.
The Reconciler is the Foreman. The Architect (React) hands a blueprint to the Foreman (Reconciler). The Foreman looks at the construction site and shouts orders: "Build a wall here!", "Paint that text green!", "Tear down that box!"
To understand this, let's look at the "Hello World" of interactivity: a Counter.
const Counter = () => {
const [count, setCount] = React.useState(0);
// Updates count every second
useInterval(() => {
setCount(c => c + 1);
}, 1000);
return <Text color="green">Count: {count}</Text>;
};
The Challenge:
count from 0 to 1.<Text>Count: 1</Text>.React is designed to be universal. It can run on the web (ReactDOM), on phones (ReactNative), and thanks to Ink, in terminals.
To make React work in a new environment, you provide a Host Config. This is a dictionary of methods that tells React how to translate its commands into specific actions for that environment.
Ink implements react-reconciler by providing methods like:
createInstance: "Make a new Box."appendChild: "Put this Text inside that Box."commitUpdate: "Change the color property."Let's trace what happens when our Counter component starts up.
createNode.createTextNode.reconciler.tsLet's look at the actual code Ink uses to implement this "Foreman." This file is the bridge between the two worlds.
createInstance)
When React encounters a <Box> or <Text>, it calls this method.
// reconciler.ts (Simplified)
createInstance(type, props, root, hostContext) {
// 1. Delegate to the DOM layer to make the object
const node = createNode(type);
// 2. Apply initial props (styles, attributes)
for (const [key, value] of Object.entries(props)) {
applyProp(node, key, value);
}
return node;
}
Explanation:
color="green") so the node is born ready.appendChild)React builds trees. It needs to connect parents to children.
// reconciler.ts (Simplified)
appendChild(parentNode, childNode) {
// Delegate to DOM layer
appendChildNode(parentNode, childNode);
}
Explanation:
This wrapper calls appendChildNode in dom.ts, which pushes the child into the parent's array and connects the underlying Yoga layout nodes together.
commitUpdate)
This is where the magic of the Counter example happens. When count changes, React calls this.
// reconciler.ts (Simplified)
commitUpdate(node, type, oldProps, newProps) {
// 1. Figure out what actually changed
const propsDiff = diff(oldProps, newProps);
// 2. Apply only the changes
if (propsDiff) {
// If style changed, update it
if (propsDiff.style) {
setStyle(node, propsDiff.style);
}
// If other attributes changed, update them
setAttribute(node, key, value);
}
}
Explanation:
diff). If only the color changed, it only updates the color property.setStyle marks the node as "dirty," which tells the Layout Engine it needs to re-calculate positions soon.commitTextUpdate)For our counter, the text string itself is changing.
// reconciler.ts (Simplified)
commitTextUpdate(node, oldText, newText) {
// Update the raw string value
setTextNodeValue(node, newText);
}
Explanation:
Simple and direct. It updates the .nodeValue property on the Ink text node.
Let's re-run the Counter update with our new knowledge.
count becomes 1.commitTextUpdate(textNode, "Count: 0", "Count: 1").textNode in memory now holds the string "Count: 1".dirty = true.Because the node is dirty, Ink knows the layout might have changed (maybe "10" is wider than "9"?). This triggers the next phase of the pipeline.
In this chapter, you learned:
createInstance, commitUpdate).At this point, we have a DOM tree that updates automatically when React state changes. But a terminal application isn't just about outputβit's about interaction.
How does Ink handle keystrokes like Ctrl+C or moving a selection with Arrow Keys?
Next Chapter: Input Processing Pipeline
Generated by Code IQ