Bug Report: SelectInput navigation indicator not updating on arrow keys
Component
SelectInput from @orchetron/storm
Description
When using SelectInput with keyboard navigation (↑/↓), the highlight indicator does not move visually in the terminal, even though the internal selection state updates correctly.
Root Cause Analysis
1. The Problem
SelectInput uses a useRef (highlightRef) to track the highlighted index:
const highlightRef = useRef(initialIndex);
The highlighted index is computed during React's render:
const effectiveIndex = filteredItems.length === 0 ? 0 : highlightRef.current;
2. What Happens on Key Press
When the user presses ↑/↓:
handleInput updates highlightRef.current
handleInput calls requestRender()
3. The Bug: requestRender() → fastRepaint()
requestRender() maps to pipeline.scheduleFastRepaint(), which calls fastRepaint(). This is a custom terminal repaint that:
- Skips React reconciliation entirely
- Only repaints the existing committed fiber tree
- Does not re-run component functions
Since effectiveIndex is computed during React's render (not during repaint), the indicator never updates visually even though highlightRef.current has changed.
4. Compounding Factor: React.memo
SelectInput is wrapped in React.memo. Even if the parent re-renders, React.memo prevents SelectInput from re-rendering because the props (items, onSelect, etc.) have stable references.
Expected Behavior
The highlight indicator should move immediately when the user presses ↑/↓.
Actual Behavior
The highlight indicator stays at the initial position. The internal state updates correctly (selection works), but the visual feedback is missing.
Workaround Used
Force React.memo to allow re-renders by changing a prop on every keypress:
const [navVersion, bumpNav] = useReducer((x: number) => x + 1, 0);
useInput(
(event) => {
if (event.key === 'up' || event.key === 'down' || ...) {
bumpNav();
}
},
{ isActive: visible },
);
<SelectInput
items={items}
onSelect={onSelect}
aria-label={String(navVersion)} // Forces re-render on change
/>
Suggested Fix
Either:
- Use state instead of ref for
highlightRef so React naturally re-renders on change
- Call a full React render instead of
fastRepaint() when the highlight changes
- Remove
React.memo from SelectInput (though this has performance implications)
Environment
@orchetron/storm version: 0.2.0
- Node version: v22.14.0
Bug Report: SelectInput navigation indicator not updating on arrow keys
Component
SelectInputfrom@orchetron/stormDescription
When using
SelectInputwith keyboard navigation (↑/↓), the highlight indicator does not move visually in the terminal, even though the internal selection state updates correctly.Root Cause Analysis
1. The Problem
SelectInputuses auseRef(highlightRef) to track the highlighted index:The highlighted index is computed during React's render:
2. What Happens on Key Press
When the user presses ↑/↓:
handleInputupdateshighlightRef.currenthandleInputcallsrequestRender()3. The Bug:
requestRender()→fastRepaint()requestRender()maps topipeline.scheduleFastRepaint(), which callsfastRepaint(). This is a custom terminal repaint that:Since
effectiveIndexis computed during React's render (not during repaint), the indicator never updates visually even thoughhighlightRef.currenthas changed.4. Compounding Factor:
React.memoSelectInputis wrapped inReact.memo. Even if the parent re-renders,React.memopreventsSelectInputfrom re-rendering because the props (items,onSelect, etc.) have stable references.Expected Behavior
The highlight indicator should move immediately when the user presses ↑/↓.
Actual Behavior
The highlight indicator stays at the initial position. The internal state updates correctly (selection works), but the visual feedback is missing.
Workaround Used
Force
React.memoto allow re-renders by changing a prop on every keypress:Suggested Fix
Either:
highlightRefso React naturally re-renders on changefastRepaint()when the highlight changesReact.memofromSelectInput(though this has performance implications)Environment
@orchetron/stormversion: 0.2.0