Skip to content

Conversation

@victorlin
Copy link
Member

@victorlin victorlin commented Sep 17, 2025

Description of proposed changes

This co-locates handling of the escape key within the component that it is used for, where clearSelectedNode is already used as an onClick callback to close the modal.

Related issue(s)

Thought of this while investigating #2011. While it doesn't fix the bug, it helps answer the first question that came to mind: "how different are the esc and click handling code paths?"

Checklist

@victorlin victorlin self-assigned this Sep 17, 2025
@nextstrain-bot nextstrain-bot temporarily deployed to auspice-victorlin-move--9ade09 September 17, 2025 22:18 Inactive
jameshadfield
jameshadfield approved these changes Oct 30, 2025
Comment on lines 251 to 263
useEffect(() => {
function handleEscapeKey(event) {
if (event.key==="Escape" && selectedNode) {
clearSelectedNode(selectedNode);
}
}
document.addEventListener('keyup', handleEscapeKey);
return function cleanup() {
document.removeEventListener('keyup', handleEscapeKey);
};
}, [selectedNode, clearSelectedNode]);

Copy link
Member

@jameshadfield jameshadfield Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One salient difference is that this will add/remove listeners every time a modal appears/disappears as opposed to a single listener set up when the tree panel mounts. In reality this won't matter because opening a modal is a one-off action, but it's something to be aware of when designing listeners.

An improvement would be to return early to avoid adding an unnecessary listener when we close the modal:

  useEffect(() => {
    if (!selectedNode) return
    ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for noting the difference. There is a way to retain the existing behavior using a ref, which I've incorporated into the main commit (c136e25). I added another commit to switch to the plain useEffect pattern with your proposed improvement above (eae3fba), which we can drop if you think the existing behavior is better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to dive back into the world of hooks one day, although I wasn't very enamored with them. Perhaps the rest of the group feel differently! Is adding an additional useRef & useEffect the recommended way? I find it somewhat convoluted to read, although I can figure out how it works. Up to you whether this is your preferred method vs. a single but slightly more costly useEffect - either works for me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used useRef & useEffect in c136e25 with the goal of retaining the existing behavior (adding the event listener once on component mount). I don't think it's the recommended way, and also find it convoluted to read.

The single useEffect in eae3fba aligns more with examples in docs, suggesting that it’s the recommended way. It's not necessarily more costly (depends on how we define "cost"), which I tried to reflect in the commit message.

@jameshadfield
Copy link
Member

jameshadfield commented Oct 30, 2025

P.S. "The Effect Hook is a direct replacement for componentDidMount and componentWillUnmount" just isn't true. Perhaps useEffect with an explicit & empty dependency array is the same, although even then I haven't checked the exact timings of when code runs relative to DOM mutations.

victorlin and others added 3 commits October 30, 2025 09:36
The new name better reflects existing usage.
This co-locates handling of the escape key within the component that it
is used for, where clearSelectedNode is already used as an onClick
callback to close the modal.

useEffect is the Hooks replacement¹ for componentDidMount and
componentWillUnmount. selectedNodeRef has been added to replicate the
behavior of adding the event listener once on component render. Without
the ref, selectedNode would need to be set as a dependency, which would
add/remove the event listener on every change of selectedNode.

¹ https://legacy.reactjs.org/docs/hooks-effect.html#effects-with-cleanup
This adds/removes the event listener on every change of selectedNode,
but has the benefit of not running the event listener on every keyup
even when no node is selected.
@victorlin victorlin force-pushed the victorlin/move-esc-modal-code branch from fbae400 to eae3fba Compare October 30, 2025 16:59
@victorlin
Copy link
Member Author

P.S. "The Effect Hook is a direct replacement for componentDidMount and componentWillUnmount" just isn't true.

Reworded in c136e25.

@jameshadfield jameshadfield merged commit d87b3a3 into master Oct 30, 2025
16 checks passed
@jameshadfield jameshadfield deleted the victorlin/move-esc-modal-code branch October 30, 2025 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants