Welcome to the final chapter of our tutorial series!
In the previous chapter, Custom Selection Input, we gave the user the ability to make choices using their keyboard.
However, we have introduced a potential problem. If the user runs a command, sees our menu, and then walks away to get coffee, their terminal is blocked. It will wait forever for an input that isn't coming.
In this chapter, we will implement Time-Limited Interactions. We will create a "Self-Destruct" mechanism that automatically dismisses the menu if the user ignores it.
Imagine you are playing a video game. You enter a room, and a bomb starts counting down. You have 30 seconds to cut the wire. If you do nothing, the game decides for you (BOOM).
In our CLI tool, we want a similar (but less explosive) concept.
The Use Case:
To make this happen, we use three tools from the React toolbox.
setTimeoutThis is a standard JavaScript function. It allows us to say: "Execute this code X milliseconds from now."
clearTimeoutThis is the "Abort Button." If the user answers the question manually before the timer runs out, we must cancel the timer. If we don't, the timer will still fire later and might cause errors.
useEffectThis is a React Hook. It allows us to set up the timer exactly when the component appears on the screen, and clean it up when the component goes away.
Let's look at how we implement this logic inside PluginHintMenu.tsx.
First, we decide how long to wait. Computers count time in milliseconds (1000ms = 1 second).
// 30 seconds
const AUTO_DISMISS_MS = 30_000;
This part is a little tricky. React functions can sometimes get "stale" (holding onto old data). We use useRef to ensure our timer always calls the most current version of our response function.
// Create a reference that always points to the current function
const onResponseRef = React.useRef(onResponse);
// Update it whenever the component renders
onResponseRef.current = onResponse;
Explanation: Think of onResponseRef as a whiteboard. Every time the component updates, we erase the whiteboard and write the newest version of the onResponse function on it. The timer just looks at the whiteboard.
Now we use useEffect to start the countdown.
React.useEffect(() => {
// Start the timer
const timeoutId = setTimeout(
(ref) => ref.current('no'), // The Action: Answer "No"
AUTO_DISMISS_MS, // The Delay: 30 seconds
onResponseRef // The Argument passed to the action
);
// The Cleanup: Stop timer if user leaves
return () => clearTimeout(timeoutId);
}, []);
Explanation:
setTimeout starts ticking.ref.current('no'). This acts exactly as if the user selected "No".return function inside useEffect runs if the component is removed before the time is up (e.g., the user pressed "Enter"). It calls clearTimeout to stop the clock.This logic creates a "race" between the User and the Timer. Whoever finishes first decides the outcome.
We place this logic right at the top of our PluginHintMenu component. It runs silently in the background while the UI waits for user input.
Here is the context within the file:
export function PluginHintMenu({ onResponse, ...props }: Props) {
// 1. Setup the Ref
const onResponseRef = React.useRef(onResponse);
onResponseRef.current = onResponse;
// 2. Setup the Timer
React.useEffect(() => {
const timeoutId = setTimeout(
ref => ref.current('no'),
AUTO_DISMISS_MS,
onResponseRef
);
return () => clearTimeout(timeoutId);
}, []);
// 3. Render the UI (as seen in Chapter 2 & 4)
return (
<PermissionDialog title="Plugin Recommendation">
{/* ... content ... */}
</PermissionDialog>
);
}
This ensures that the Permission Dialog Wrapper and the Custom Selection Input never get stuck on the screen indefinitely.
Congratulations! You have completed the tutorial series for ClaudeCodeHint.
Let's recap what you have built:
<Box> and <Text>.By combining these concepts, you have created a CLI tool that is not only functional but also intelligent, beautiful, and user-friendly.
Happy Coding!
Generated by Code IQ