NavDropdown from react-bootstrap will not collapse

136 views Asked by At

I'm using react-bootstrap 2.9.2 (bootstrap 5.3.2) to attempt to create a simple Navbar, with the first item in the Nav menu being a Dropdown list. The NavDropdown expands and the routes in the dropdown items work, but the dropdown never collapses. My understanding was that it should collapse in several scenarios by default, based on the behavior of the react-bootstrap example here. When expanded, I'd like it to collapse:

  • when clicking the parent of the dropdown again ("Home" in my example below)
  • when clicking a child within the dropdown (which navigates to a different route)
  • when clicking outside the Navbar

None of these actions collapses the dropdown for my project. The only way I can currently collapse the dropdown is by refreshing the page. How can I get the dropdown to collapse for these 3 typical scenarios?

Below is some of the code I believe might be relevant, but happy to provide more if needed.

index.js:

import ReactDOM from 'react-dom/client';
import App from './App';
import { BrowserRouter } from 'react-router-dom';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

App.js:

import './App.css'
import React from 'react';
import MainPage from './components/MainPage'
import { Header } from './components/Header';
import { NavHeader } from './components/NavHeader';
import { Routes, Route } from 'react-router-dom';
import TransactionsPage from './components/TransactionsPage';

function App() {
  // This is what gets rendered on the page.
  return (
    <>
      { /* Display the decorative header, and navigation bar. */}
      <Header />
      <NavHeader />
      { /* Based on the selected route path, load a specific page. Index page is the default. */}
      <Routes>
        <Route exact path='/' element={<MainPage/>} />
        <Route path='/home/:category' element={<MainPage/>} />
        <Route path='/transactions' element={<TransactionsPage />} />
      </Routes>
    </>
  )
}

export default App;

NavHeader.js:

import { Link, NavLink } from 'react-router-dom';
import { useState } from 'react';
import './NavHeader.css'

// Returns the navigation bar header.
export const NavHeader = () => {
    const [expanded, setExpanded] = useState(false);

    return (
        <Navbar collapseOnSelect>
            <Container>
                <Navbar.Toggle aria-controls="responsive-navbar-nav" />
                <Navbar.Collapse id="responsive-navbar-nav">
                    <Nav className="nav-header">
                        {/* Use Link from react-router-dom to define the routing links. */}
                        <NavDropdown
                                title="Home" 
                                id="collapsible-nav-dropdown" 
                                className="vertical-dropdown"
                                onToggle={() => setExpanded(!expanded)}
                                show={expanded}
                                >
                            <NavDropdown.Item as={NavLink} to="/home/stock">Stocks</NavDropdown.Item>
                            <NavDropdown.Item as={NavLink} to="/home/etf">ETFs</NavDropdown.Item>
                            <NavDropdown.Item as={NavLink} to="/home/full">Full</NavDropdown.Item>
                        </NavDropdown>
                        <Link to="/transactions" className="nav-item">Transactions & Performance</Link>
                    </Nav>
                </Navbar.Collapse>
            </Container>
        </Navbar>
    );
}

NavHeader.css:

.nav-header {
    height: 4rem;
    display: flex;
    align-items: center;
    background-color: black;
}

.nav-item {
    color: grey;
    margin-left: 20px;
    margin-right: 20px;
    font-size: 20px;
}

.vertical-dropdown .dropdown-menu {
    display: flex;
    flex-direction: column;
}

.vertical-dropdown .dropdown-item {
    width: 100%;
    text-align: left;
}

Here's what the Navbar looks like with the dropdown expanded:

enter image description here

Below is a list of a few of the things I've tried already that have not worked:

  • Setting expanded state variable to NavDropdown variable show, open, and expanded. All with no effect.
  • Tried various iterations of the Dropdown examples instead, including DropdownButton with onToggle, onBlur, autoClose, etc, such as the solution here. None of these options would allow the dropdown to collapse as expected.

EDIT:

Based on the response, I've now modified the one section of the NavHeader.js file to be:

.vertical-dropdown.show .dropdown-menu {
    display: flex;
    flex-direction: column;
}

Now, there is better responsiveness when clicking a child in the dropdown, or clicking outside the Navbar. However, these click actions do not hide the dropdown; it "collapses" from a vertical list to a horizontal list, but still visible! How can I get the dropdown to truly collapse and actually hide the options without needing a full page refresh?

enter image description here

EDIT2:

When inspecting the drop-down element when "collapsed" (but still visible and displayed horizontally), it appears there is a still a display: block applied to this element:

enter image description here

I'm not sure where this comes from, as I don't see it in other examples. As a hacky-workaround, I was able to achieve the desired drop-down behavior (where it is actually hidden when clicking away) by adding this piece to my Navbar.css file:

.vertical-dropdown .dropdown-menu {
    display: none;
}
2

There are 2 answers

8
johannchopin On

The issue come from this css:

.vertical-dropdown .dropdown-menu {
  display: flex;
  flex-direction: column;
}

It defines that .dropdown-menu will always be display: flex. However internally the dropdown uses display: none to hide it.

You can fix that by specifying that the .vertical-dropdown should be open to apply the css using the .show class:

.vertical-dropdown.show .dropdown-menu {
  display: flex;
  flex-direction: column;
}

See the Codesandbox: Edit unruffled-moon

On another note the .vertical-dropdown .dropdown-menu is already a flex element in the column direction so you can actually remove all this extra css

1
samarth sangam On

Don't use className on NavDropdown instead use bsPrefix and change the styling accordingly. By using className you are removing the bootstrap classes.

import { Link, NavLink } from 'react-router-dom';
import { useState } from 'react';
import './NavHeader.css'

// Returns the navigation bar header.
export const NavHeader = () => {
    const [expanded, setExpanded] = useState(false);

    return (
        <Navbar collapseOnSelect>
            <Container>
                <Navbar.Toggle aria-controls="responsive-navbar-nav" />
                <Navbar.Collapse id="responsive-navbar-nav">
                    <Nav bsPrefix="nav-header">
                        {/* Use Link from react-router-dom to define the routing links. */}
                        <NavDropdown
                                title="Home" 
                                id="collapsible-nav-dropdown" 
                                bsPrefix="vertical-dropdown"
                                onToggle={() => setExpanded(!expanded)}
                                show={expanded}
                                >
                            <NavDropdown.Item as={NavLink} to="/home/stock">Stocks</NavDropdown.Item>
                            <NavDropdown.Item as={NavLink} to="/home/etf">ETFs</NavDropdown.Item>
                            <NavDropdown.Item as={NavLink} to="/home/full">Full</NavDropdown.Item>
                        </NavDropdown>
                        <Link to="/transactions" className="nav-item">Transactions & Performance</Link>
                    </Nav>
                </Navbar.Collapse>
            </Container>
        </Navbar>
    );
}