Welcome to the final chapter of our series! In the previous chapter, Component Integration, we built a beautiful visual component using React and Ink. We learned how to display it, but we haven't fully explained how to stop it.
If you ran the code from the previous chapters, you might have noticed something important: once the command starts, the terminal is "busy." The prompt doesn't come back immediately. We need a way to tell the application, "I am finished now."
Imagine you are watching a movie in a cinema. The movie ends, the screen goes black, but the doors are locked. You are stuck there forever!
In CLI programming, when we take control of the screen to show a UI (like our Stats dashboard), we are locking the doors. The parent process (your terminal shell) is waiting politely for us to finish.
Lifecycle Control is the mechanism we use to unlock the doors and let the user return to their command prompt.
onDone CallbackWe solve this using a pattern called a Callback.
Think of onDone as a physical key passed down a relay race:
Let's trace the journey of this "key" through our files.
Open src/commands/stats/stats.tsx. Look at the function arguments.
// stats.tsx
export const call: LocalJSXCommandCall = async (onDone) => {
// onDone is a function: () => void
// calling it signals that we are finished.
// We pass it down to the UI component as a prop named 'onClose'
return <Stats onClose={onDone} />;
};
Explanation:
onDone automatically when it calls your function.onDone() here immediately. If we did, the app would open and close instantly!<Stats /> so the user can decide when to close it.
Now look at src/components/Stats.tsx. This is where the user interaction happens.
In a real CLI tool, we usually wait for a specific key press (like 'q' or 'Esc') to close the app. We use a hook from Ink called useInput.
import { useInput } from 'ink';
export const Stats = ({ onClose }: { onClose: () => void }) => {
// This hook listens for keyboard events
useInput((input, key) => {
if (input === 'q' || key.escape) {
// The user wants to exit!
// We call the function passed from the parent.
onClose();
}
});
return <Text>Press 'q' to exit.</Text>;
};
Explanation:
useInput: This behaves like an event listener. It watches every keystroke.onClose(): When this runs, it triggers the onDone function back in the handler, which signals the CLI to stop.Why does the application pause while our component is visible? It uses Promises.
A Promise in JavaScript is like a buzzer at a restaurant. It represents a future event. The CLI is programmed to "await" that buzzer.
onDone signal.onDone().Note: You do not need to write this code. This is a simplified view of the framework code running your command.
The core framework wraps your command in a Promise wrapper to manage this lifecycle.
// Framework internal logic
function runCommandWrapper(userCommand) {
return new Promise((resolve) => {
// We create the onDone function
// When called, it resolves the promise
const onDone = () => {
resolve("Success");
};
// We run YOUR code, passing the resolver
userCommand.call(onDone);
});
}
Walkthrough:
new Promise: This tells JavaScript "Don't finish this function until resolve is called."const onDone: We create a small function that simply calls resolve.userCommand.call(onDone): We hand that trigger to your code.onDone
If you write return <Stats /> without passing the prop, your component will render, but the "Exit" button won't be connected to anything. The user will press 'q', and nothing will happen.
onDone too earlyIf you write:
export const call = async (onDone) => {
onDone(); // โ Called immediately!
return <Stats ... />;
}
The application will flash on the screen for 1 millisecond and then exit immediately.
Congratulations! You have completed the stats project tutorial.
Let's recap what we built:
stats command in Command Configuration.You now possess the foundational knowledge to build interactive, performant, and user-friendly CLI tools. You understand not just how to write the code, but why the architecture is designed this way.
Happy coding!
Generated by Code IQ