[Bug Report] ImagePane crashes with TypeError for image_history when selected is undefined or out of bounds
Bug Description
While working on the frontend visualization components, I noticed a reproducible crash in ImagePane.js when rendering image_history panes. If an image_history window payload is received without a selected field (or if the selection index becomes invalid after history truncation or updates), the frontend throws an unhandled TypeError inside React's render loop and crashes the entire dashboard grid.
Looking into how other history-based panes are implemented (such as PlotPane.js and exportTemplate.js), PlotPane explicitly guards against missing selection indices and out-of-bounds array access. ImagePane.js currently misses these defensive checks.
Code Analysis & Root Cause
Unchecked State Initialization: In
js/panes/ImagePane.js:41:const [actualSelected, setActualSelected] = useState(props.selected);When
props.selectedis not explicitly provided in the window properties,actualSelectedis initialized toundefined. In comparison,PlotPane.jshandles this via:const [actualSelected, setActualSelected] = useState( isHistory ? selected || 0 : 0 );Unconditional Prop Sync: In
js/panes/ImagePane.js:243-245:useEffect(() => { setActualSelected(selected); }, [selected]);If a parent re-render occurs where
selectedisundefined, this unconditionally overwrites any valid internal state back toundefined.Array Indexing Without Bounds Clamping: In
js/panes/ImagePane.js:396:content = content[actualSelected];If
actualSelectedisundefined(or if it points to an index beyondcontent.lengthafter images were pruned or updated),contentbecomesundefined.Unhandled Property Access during Render: Immediately following that line (lines 405 and 408):
useEffect(() => { let cancelled = false; typesetMathJax(captionRef.current, () => cancelled); return () => { cancelled = true; }; }, [content.caption]); if (content.caption) { ... }Evaluating
content.captionthrowsTypeError: Cannot read properties of undefined (reading 'caption'). Because Visdom does not wrap individual grid panes inside separate React error boundaries, this uncaught exception causes the whole layout grid to unmount.
Reproduction Steps
- Launch a Visdom server:
python -m visdom.server - Open the web interface at
http://localhost:8097/ - In a Python terminal or client script, send an
image_historywindow payload whereselectedis omitted (or load an environment JSON saved withoutselected):import visdom viz = visdom.Visdom() # Send an image_history window payload without setting the selected index viz._send({ "data": [{ "type": "image_history", "content": { "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "caption": "Frame 1" } }], "win": "img_history_test", "eid": "main", "opts": {"show_slider": True} }, endpoint="events") - Check the browser console.
Expected behavior
The ImagePane component should gracefully handle missing selected props by defaulting to index 0 (or content.length - 1), clamp the index within valid array bounds [0, content.length - 1], and safely access properties using optional chaining so that the pane renders cleanly without breaking the grid.
Client logs:
Uncaught TypeError: Cannot read properties of undefined (reading 'caption')
at ImagePane (ImagePane.js:405)
at renderWithHooks (react-dom.development.js:14985)
at mountIndeterminateComponent (react-dom.development.js:17811)
at beginWork (react-dom.development.js:18596)Server logs:
200 POST /events (127.0.0.1) 1.45msAdditional context
This bug affects any scenario where:
- An environment is reloaded from disk containing
image_historydata exported without explicitselectedkeys. - A custom API client / mock sends pane data directly to
/events. - An active
image_historywindow has its frames truncated (e.g., when reaching memory capacity limits) while a user had a higher slider index selected.
Source: fossasia/visdom