In the previous chapter, Reactive State Hook, we brought our session command to life by connecting it to live data. The QR code now generates automatically when the network URL is ready.
However, there is one final problem. At the bottom of our screen, we wrote: "(press esc to close)". But if you press Esc right now... nothing happens. The program just sits there. You have to force-quit it (usually with Ctrl+C), which feels broken.
In this final chapter, we will make the application listen to the user.
Imagine playing a video game. You see instructions on the screen: "Press A to Jump".
jump() function."
For our session command, we want to create a graceful exit.
Escape key.
The Solution: We use a hook called useKeybinding. It acts as the bridge between the keyboard and your code functions.
You might expect to write code like if (key === 'Escape').
However, our system uses Abstract Actions instead of raw key names.
We use an action ID called 'confirm:no'.
Esc to close things. Another might prefer pressing n (for "No").'confirm:no', we group these keys together under one logical intent: "I want to cancel or leave."
We need to edit our session.tsx file one last time. We will tell our component to listen for the "Cancel/No" action.
We need a function to run when the user wants to leave. In our code, this function is passed down as a "prop" called onDone.
type Props = {
// A function provided by the system to close the command
onDone: () => void;
};
Now we import and use the hook inside our component.
import { useKeybinding } from '../../keybindings/useKeybinding.js';
function SessionInfo({ onDone }: Props) {
// "When the user signals 'No' or 'Cancel' (Escape key),
// execute the 'onDone' function."
useKeybinding('confirm:no', onDone);
// ... rest of the component
}
Explanation:
'confirm:no': This is the trigger. It maps to standard cancel keys like Escape.onDone: This is the action. When the trigger fires, this function runs, closing the UI.Sometimes, multiple parts of the screen might be listening for keys. To avoid confusion, we can name the context.
useKeybinding('confirm:no', onDone, {
context: 'Confirmation'
});
Output:
Now, when the QR code is on the screen, if the user presses Esc, the onDone function fires. The session command finishes, and the terminal prompt reappears.
How does a React component inside a text-based terminal know you pressed a physical key?
It involves a chain of events passing through the Input Manager.
\x1b for Escape).confirm:no).useKeybinding hook for that intent.onDone) bound to that component.
The useKeybinding hook essentially registers your component into a global list of listeners when the component appears (mounts), and removes it when it disappears (unmounts).
Here is a simplified version of what the hook does:
// Simplified pseudo-code
function useKeybinding(actionId, callback) {
useEffect(() => {
// 1. Register: "I am interested in 'confirm:no'"
const listenerId = KeybindingSystem.register(actionId, callback);
// 2. Cleanup: "I am leaving, stop sending me keys"
return () => {
KeybindingSystem.unregister(listenerId);
};
}, [actionId, callback]);
}
Why is this cleanup important?
If we didn't remove the listener, the application might try to run onDone even after the command has closed, which would cause the program to crash. React handles this lifecycle automatically with useEffect.
Congratulations! You have successfully built the session command from scratch.
Let's review the journey of the architecture:
name, aliases) and rules (isEnabled).Box and Text.You now understand the core pillars of building a professional, high-performance CLI tool using this architecture. You can apply these same patterns to create complex forms, selection lists, and interactive dashboards in the terminal!
End of Tutorial.
Generated by Code IQ