Fixing a Stuck MUI Select Menu During React Router Navigation + Suspense
I ran into a surprisingly annoying issue with Material UI’s Select component while navigating between routes in React.
The bug only happened on the first selection.
When selecting a menu item:
- the route changed
- the page suspended
- a Suspense fallback rendered
- and the Select menu stayed visually stuck open
It looked like MUI never finished closing the dropdown.
The Setup
I had something like this:
<Select value={workspace} onChange={handleWorkspaceChange}>
{workspaces.map((workspace) => (
<MenuItem key={workspace.url} value={workspace.url}>
{workspace.name}
</MenuItem>
))}
</Select>And inside the change handler:
const handleWorkspaceChange = (e: SelectChangeEvent) => {
const value = e.target.value;setWorkspace(value); navigate(`/dashboard${value}`);
};
Looks harmless.
But because navigation triggered a rerender + Suspense fallback immediately, the Select component unmounted before MUI completed its internal close cycle.
The result:
- backdrop remained
- menu portal stayed mounted
- dropdown appeared stuck open
Only on the first interaction.
Why This Happens
MUI Select uses:
- a portal
- internal open state
- transition lifecycle events
When React Router navigation immediately triggers:
- Suspense
- rerender
- unmount
…the Select never gets to properly fire its close lifecycle.
So the menu portal survives briefly in an inconsistent state.
The Wrong Fixes I Tried
I tried:
blur()- refs
setTimeoutrequestAnimationFrame- checking previous values
- memoization
useCallback
Some reduced the issue but none fully solved it.
Get Zubeyr Anwar’s stories in your inbox
Join Medium for free to get updates from this writer.
Because the real issue was timing.
The Real Fix
The solution was simple:
Do NOT navigate inside onChange
Instead:
- store the pending value
- close the Select first
- navigate only after
onClose
Working Solution
const [open, setOpen] = useState(false);const pendingWorkspaceRef = useRef<string | null>(null);const handleWorkspaceChange = useCallback(
(e: SelectChangeEvent) => {
const value = e.target.value; if (value === workspace) return; // store pending navigation
pendingWorkspaceRef.current = value; // update local state
setWorkspace(value); // close menu first
setOpen(false);
},
[workspace],
);const handleClose = useCallback(() => {
setOpen(false); const pending = pendingWorkspaceRef.current; if (!pending) return; pendingWorkspaceRef.current = null; // wait until menu fully closes
requestAnimationFrame(() => {
navigate(`/dashboard${pending}`);
});
}, [navigate]);
Then wire it up:
<Select
open={open}
onOpen={() => setOpen(true)}
onClose={handleClose}
value={workspace}
onChange={handleWorkspaceChange}
>
{workspaces.map((workspace) => (
<MenuItem key={workspace.url} value={workspace.url}>
{workspace.name}
</MenuItem>
))}
</Select>Why This Works
The flow becomes:
- User selects item
- Selection state updates
- Select closes normally
onClosefires- Navigation happens afterward
This allows MUI to clean up:
- portal
- backdrop
- transitions
- focus management
before Suspense remounts the tree.
Final Thoughts
This was one of those bugs where:
- the UI looked broken
- the logic looked correct
- and the actual problem was component lifecycle timing
If you’re using:
- MUI Select
- React Router
- Suspense/lazy routes
…and your dropdown gets stuck during navigation, delaying route changes until onClose is a working workaround.






