In the previous chapter, Command Module Definition, we set up the "catalog card" for our command. We told the CLI that a command named memory exists and where to find its code.
Now, we are going to look at what happens the exact moment the user hits Enter.
Imagine you go to a restaurant. You order dinner. The waiter immediately places a plate in front of youโbut it's empty. Five seconds later, they throw the steak on it. Five seconds after that, they dump the potatoes on top.
It works, but it feels messy, right?
In software, this is called a Flash of Unstyled Content or a "loading flicker." If we launch our command and immediately show the UI before we have loaded the data (the memory files), the user might see an empty list or a spinning wheel for a split second.
The Solution: We want to act like a good chef: prepare the ingredients (data) before serving the plate (the UI). We use the Async Command Lifecycle to pause execution until everything is ready.
call Function
In our implementation file (memory.tsx), the magic happens inside a specific function exported as call. This is the bridge between the Command Line Interface (CLI) and the React User Interface.
We start by defining the function. Note the async keyword. This tells the application: "Hang on, I might need some time to finish this."
import type { LocalJSXCommandCall } from '../../types/command.js';
// The CLI calls this function when 'memory' is run
export const call: LocalJSXCommandCall = async (onDone) => {
onDone: This is a tool we pass down to the UI. It allows the UI to eventually say, "I'm finished now, you can close the app."Before we fetch new data, we want to make sure we aren't accidentally using old, stale data from the last time the command ran.
// clearMemoryFileCaches is a utility helper
clearMemoryFileCaches();
This is the most critical step. We ask for the files, and we use await to pause the function.
// Fetch data AND wait for it to finish
await getMemoryFiles();
getMemoryFiles() returns the list of files.Finally, once the data is ready (cached in memory), we return the React component.
// Return the actual interactive component
return <MemoryCommand onDone={onDone} />;
};
onDone to the component so it can handle exiting the app later.Let's look at the sequence of events. Notice how the "User Interface" doesn't appear until the "Data" is ready.
If we didn't use await in the call function, the CallFn would trigger the UI immediately, resulting in that "Empty Plate" effect we discussed earlier.
The file memory.tsx handles this logic. While the call function is simple, it relies on complex helpers under the hood.
The Wrapper Component:
You might notice we return <MemoryCommand />. This is a React component. In the next chapter, we will build that component.
Why not use React Suspense? React has a feature called "Suspense" that handles loading states. In fact, our code does use Suspense inside the component as a backup!
However, using the Async Command Lifecycle (the await in the call function) provides a better user experience for the initial load. It ensures the terminal window doesn't resize or flicker. It makes the command feel "solid" and native.
In this chapter, we learned:
call function, which is the asynchronous entry point for the command.
Now that our data is pre-loaded and our call function has returned a Component, we need to actually build that Component to show something on the screen.
Next Chapter: Interactive CLI Component
Generated by Code IQ