I am developing a Navbar using React-Bootstrap that contains a cart-icon button that displays my Offcanvas as a minicart (a cart where you can add items to it throughout the website without having to go to the checkout cart to view your cart contents.)
My issue is that when I am toggling my offcanvas open and close it also triggers my navbar.toggle to open and close the (bottom-half) of my navbar. I have added console.logs to debug when the Navbar.Toggle is clicked and the logs are not fired when I open and close the Offcanvas yet it still acts as if the Navbar.Toggle is being clicked to open and close the (bottom-half) of my navbar. In my Navigation.jsx code (code will be provided) my Navbar has a className=”navbar-bottom-half”. If, in my Navigation.css file i add the style: .navbar-bottom-half{ transition: none; }, it solves my issue when i am viewing it on desktop screens. But, if i add that same css code in my media query for the mobile view it does not solve my issue. Plus I feel like that css code is kind of hacky and not the correct way to solve my problem. I have ran into several issues kind of like this while using react-bootstrap but I have always been able to solve it in a “non-hacky” manner. Any and all feedback is much appreciated. Thank you for taking your time to read and potentially solve my issue.
In my details of my problem i discussed the ways I tried to solve the problem above. I am going to present my code below here.
Navigation.jsx (parent component handling state. I have added several comments for clarity, as this component consists of several “reusable components”.)
import React, { useState, useEffect } from "react";
import { Navbar, Container, Row, Col, Button, Nav } from "react-bootstrap";
import Logo from "./Logo";
import SearchBar from "./SearchBar";
import UserActions from "./UserActions";
import MiniCart from "./MiniCart";
import AccountModal from "./AccountModal";
import NavItems from "./NavItems";
import StartOrderModal from "./StartOrderModal";
import "./css/Navigation.css";
export default function Navigation({
popoverVisible,
togglePopover,
badgeCount,
}) {
// State for account modal visibility
const [accountModalVisible, setAccountModalVisible] = useState(false);
const toggleAccountModal = () => setAccountModalVisible(!accountModalVisible);
// State for minicart visibility
const [minicartVisible, setMinicartVisible] = useState(false);
const toggleMinicart = () => setMinicartVisible(!minicartVisible);
// State for favorites
const [favorites, setFavorites] = useState([]);
// State for Start Order Modal visibility
const [startOrderVisible, setStartOrderVisible] = useState(false);
const toggleStartOrder = () => setStartOrderVisible(!startOrderVisible);
// Load favorites from localStorage on component mount
useEffect(() => {
const storedFavorites = JSON.parse(localStorage.getItem("Favorites")) || [];
setFavorites(storedFavorites);
}, []);
// Function to update favorites
const handleUpdateFavorites = (newFavorites) => {
setFavorites(newFavorites);
localStorage.setItem("Favorites", JSON.stringify(newFavorites));
};
const handleNavbarToggleClick = () => {
console.log("Navbar.Toggle clicked");
};
return (
<Navbar expand="lg" className="hth-navbar flex-column">
{/* Top half of the navbar */}
<Container fluid className="navbar-top">
<Row className="navbar-top-half align-items-center justify-content-between w-100">
{/* Logo */}
<Col
xs={3}
lg={2}
className="d-flex align-items-center justify-content-start"
>
<Logo />
</Col>
{/* User Actions for Mobile */}
<Col xs={6} className="d-flex d-lg-none justify-content-center">
<UserActions
popoverVisible={popoverVisible}
togglePopover={togglePopover}
badgeCount={badgeCount}
toggleMinicart={toggleMinicart}
toggleAccountModal={toggleAccountModal}
/>
</Col>
{/* Toggle for Mobile */}
<Col xs={3} className="d-flex d-lg-none justify-content-end">
<Navbar.Toggle
aria-controls="navbar-nav"
className="navbar-toggle"
onClick={handleNavbarToggleClick}
/>
</Col>
{/* Search Bar for Desktop */}
<Col lg={6} className="d-none d-lg-flex align-items-center">
<SearchBar />
</Col>
{/* User Actions for Desktop */}
<Col lg={4} className="d-none d-lg-flex justify-content-end">
<UserActions
popoverVisible={popoverVisible}
togglePopover={togglePopover}
badgeCount={badgeCount}
toggleMinicart={toggleMinicart}
toggleAccountModal={toggleAccountModal}
/>
</Col>
</Row>
{/* Search Bar for Mobile */}
<Row className="w-100 justify-content-center search-bar-row d-lg-none">
<Col xs={12}>
<SearchBar />
</Col>
</Row>
</Container>
{/* Bottom half of the navbar */}
<Navbar.Collapse id="navbar-nav" className="navbar-bottom-half w-100">
<Container fluid className="px-3 d-flex justify-content-center">
<Row className="w-100 align-items-start navbar-bottom">
{/* Nav Items */}
<Col xs={12} className="d-flex flex-column align-items-start">
<Nav className="justify-content-center nav-items">
<NavItems
favorites={favorites}
onUpdate={handleUpdateFavorites}
/>
</Nav>
{/* Start Order Button */}
<Button
variant="outline-danger"
className="btn-modal start-order-button"
onClick={toggleStartOrder}
>
START ORDER
</Button>
</Col>
</Row>
</Container>
</Navbar.Collapse>
{/* MiniCart Offcanvas */}
<MiniCart
offcanvasVisible={minicartVisible}
toggleOffcanvas={toggleMinicart}
/>
{/* Account Modal */}
<AccountModal
accountModalVisible={accountModalVisible}
toggleAccountModal={toggleAccountModal}
/>
{/* Start Order Modal */}
<StartOrderModal
showStartOrder={startOrderVisible}
handleStartOrderClose={toggleStartOrder}
/>
</Navbar>
);
}
Here is my MiniCart.jsx (component that contains the Offcanvas):
import React from "react";
import { Offcanvas, Button } from "react-bootstrap";
export default function MiniCart({ offcanvasVisible, toggleOffcanvas }) {
console.log("MiniCart Component Rendered - Visible:", offcanvasVisible);
// Log when the Offcanvas is shown
const handleOffcanvasShow = () => {
console.log("MiniCart - Opening");
};
// Log when the Offcanvas is hidden
const handleOffcanvasHide = () => {
console.log("MiniCart - Closing");
};
return (
<Offcanvas
show={offcanvasVisible}
onHide={() => {
handleOffcanvasHide();
toggleOffcanvas();
}}
onShow={handleOffcanvasShow}
placement="end"
>
<Offcanvas.Header closeButton>
<Offcanvas.Title>Cart</Offcanvas.Title>
</Offcanvas.Header>
<Offcanvas.Body>
<div className="d-flex justify-content-between">
{/* temporary text for now */}
<span className="price-text">Estimated subtotal:</span>
<span className="price-amount">$0.00</span>
</div>
<hr />
{/* Going to add accordion here for different categories of items. */}
<div
className="accordion mini-cart-accordion"
id="accordionPanelsStayOpenExample"
>
<div className="accordion-item currentOrder-accordion-item">
<h2
className="accordion-header currentOrder-accordion-header"
id="panelsStayOpen-headingOne"
>
<Button
className="accordion-button currentOrder-btn"
type="button"
data-bs-toggle="collapse"
data-bs-target="#panelsStayOpen-collapseOne"
aria-expanded="true"
aria-controls="panelsStayOpen-collapseOne"
>
Current Order
</Button>
</h2>
<div
id="panelsStayOpen-collapseOne"
className="accordion-collapse collapse show"
aria-labelledby="panelsStayOpen-headingOne"
>
{/* temporary text for now */}
<div className="accordion-body currentOrder-accordion-body">
Your cart is currently empty.
</div>
</div>
</div>
<div className="accordion-item openOrders-accordion-item">
<h2
className="accordion-header openOrders-accordion-header"
id="panelsStayOpen-headingTwo"
>
<Button
className="accordion-button openOrders-btn collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#panelsStayOpen-collapseTwo"
aria-expanded="false"
aria-controls="panelsStayOpen-collapseTwo"
>
Open Orders
</Button>
</h2>
<div
id="panelsStayOpen-collapseTwo"
className="accordion-collapse collapse"
aria-labelledby="panelsStayOpen-headingTwo"
>
<div className="accordion-body openOrders-accordion-body">
Open Orders Here
</div>
</div>
</div>
</div>
</Offcanvas.Body>
<div className="offcanvas-footer mini-cart-footer">
<Button
type="submit"
variant="secondary"
className="empty-btn"
data-bs-dismiss="offcanvas"
>
Empty Cart
</Button>
<Button
as="a"
href="/checkout/checkout"
variant="primary"
className="view-cart-btn"
>
View Cart
</Button>
</div>
</Offcanvas>
);
}
Sorry folks I actually just solved my problem. I tried everything I could to not add state management on the Navbar.Collapse but that seems to be my only solution that is somewhat clean for this situation. Kind of odd as I was hoping react-bootstrap would manage this itself. But problem solved. Thank you for looking. Let me know if you know of a better solution. Thanks.
Your minicart is inside the navbar component which may be triggering event propogation from minicart to navbar.
You can try
<Offcanvas
show={offcanvasVisible}
onHide={(e) => {
e.stopPropagation();
handleOffcanvasHide();
toggleOffcanvas();
}}
onShow={(e) => {
e.stopPropagation();
handleOffcanvasShow();
}}
placement="end"
>