Skip to content

Commit 76bc0fb

Browse files
committed
fix(web): resolve React hook violations in navigation hooks
- use-user-interactions.ts: Remove try-catch wrapper around useLocation hook (hooks cannot be called inside try-catch blocks) - useNavigationEnhancements.ts: Move useEffect directly into hook body instead of inside useCallback (hooks cannot be nested in callbacks) These violations caused "Invalid hook call" errors when components using these hooks were rendered.
1 parent fd427f2 commit 76bc0fb

File tree

2 files changed

+57
-66
lines changed

2 files changed

+57
-66
lines changed

apps/web/src/hooks/use-user-interactions.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,21 +105,8 @@ export const useUserInteractions = (options: UseUserInteractionsOptions = {}): U
105105
displayName,
106106
} = options;
107107

108-
// Safely get router location (may not be available in test environments)
109-
const location = (() => {
110-
try {
111-
return useLocation();
112-
} catch {
113-
// Return a fallback location object when router is not available
114-
return {
115-
pathname: '',
116-
search: '',
117-
hash: '',
118-
state: null,
119-
key: '',
120-
};
121-
}
122-
})();
108+
// Get router location - must be called unconditionally at top level (React hooks rules)
109+
const location = useLocation();
123110

124111
// State
125112
const [recentHistory, setRecentHistory] = useState<CatalogueEntity[]>([]);

apps/web/src/hooks/useNavigationEnhancements.ts

Lines changed: 55 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -98,66 +98,70 @@ export const useNavigationEnhancements = () => {
9898
router.navigate({ to: '/' });
9999
}, [router]);
100100

101-
// Keyboard navigation handling
102-
const useKeyboardNavigation = useCallback(() => {
103-
useEffect(() => {
104-
const handleKeyDown = (event: KeyboardEvent) => {
105-
// Only handle navigation keys when not focused on input fields
106-
if (
107-
event.target instanceof HTMLInputElement ||
108-
event.target instanceof HTMLTextAreaElement ||
109-
event.target instanceof HTMLSelectElement ||
110-
(event.target as HTMLElement)?.contentEditable === 'true'
111-
) {
112-
return;
113-
}
101+
// Keyboard navigation is now handled directly in the hook
102+
// Components using this hook automatically get keyboard navigation
103+
useEffect(() => {
104+
const handleKeyDown = (event: KeyboardEvent) => {
105+
// Only handle navigation keys when not focused on input fields
106+
if (
107+
event.target instanceof HTMLInputElement ||
108+
event.target instanceof HTMLTextAreaElement ||
109+
event.target instanceof HTMLSelectElement ||
110+
(event.target as HTMLElement)?.contentEditable === 'true'
111+
) {
112+
return;
113+
}
114114

115-
// Alt + Arrow keys for navigation
116-
if (event.altKey) {
117-
switch (event.key) {
118-
case 'ArrowLeft':
119-
navigateWithKeyboard('left', event);
120-
break;
121-
case 'ArrowRight':
122-
navigateWithKeyboard('right', event);
123-
break;
124-
case 'ArrowUp':
125-
navigateWithKeyboard('up', event);
126-
break;
127-
case 'ArrowDown':
128-
navigateWithKeyboard('down', event);
129-
break;
130-
}
115+
// Alt + Arrow keys for navigation
116+
if (event.altKey) {
117+
switch (event.key) {
118+
case 'ArrowLeft':
119+
navigateWithKeyboard('left', event);
120+
break;
121+
case 'ArrowRight':
122+
navigateWithKeyboard('right', event);
123+
break;
124+
case 'ArrowUp':
125+
navigateWithKeyboard('up', event);
126+
break;
127+
case 'ArrowDown':
128+
navigateWithKeyboard('down', event);
129+
break;
131130
}
131+
}
132132

133-
// Ctrl/Cmd + [ and ] for history navigation
134-
if ((event.ctrlKey || event.metaKey)) {
135-
switch (event.key) {
136-
case '[':
137-
navigateWithKeyboard('left', event);
138-
break;
139-
case ']':
140-
navigateWithKeyboard('right', event);
141-
break;
142-
case 'k':
143-
case 'K': {
144-
// Focus search input
145-
event.preventDefault();
146-
const searchInput = document.querySelector('input[aria-label="Global search input"]');
147-
if (searchInput) {
148-
(searchInput as HTMLInputElement).focus();
149-
}
150-
break;
133+
// Ctrl/Cmd + [ and ] for history navigation
134+
if ((event.ctrlKey || event.metaKey)) {
135+
switch (event.key) {
136+
case '[':
137+
navigateWithKeyboard('left', event);
138+
break;
139+
case ']':
140+
navigateWithKeyboard('right', event);
141+
break;
142+
case 'k':
143+
case 'K': {
144+
// Focus search input
145+
event.preventDefault();
146+
const searchInput = document.querySelector('input[aria-label="Global search input"]');
147+
if (searchInput) {
148+
(searchInput as HTMLInputElement).focus();
151149
}
150+
break;
152151
}
153152
}
154-
};
153+
}
154+
};
155155

156-
document.addEventListener('keydown', handleKeyDown);
157-
return () => document.removeEventListener('keydown', handleKeyDown);
158-
}, [navigateWithKeyboard]);
156+
document.addEventListener('keydown', handleKeyDown);
157+
return () => document.removeEventListener('keydown', handleKeyDown);
159158
}, [navigateWithKeyboard]);
160159

160+
// Legacy function for backwards compatibility - now a no-op
161+
const useKeyboardNavigation = useCallback(() => {
162+
// Keyboard navigation is now automatically enabled when using this hook
163+
}, []);
164+
161165
// Get navigation context information
162166
const getNavigationContext = useCallback(() => {
163167
const parts = location.pathname.replace(/^\//, "").split("/");

0 commit comments

Comments
 (0)