React noob here. I am creating a dashboard with three parts. Sidebar | Navbar & content below navbar.
Sidebar behavior should be: open initially; auto-hide when browser-width < 498px; auto-open when browser width is increased beyond 498px; open/close when navbar toggle is hit, regardless of browser window width (ie., mobile or desktop).
I know how to do this in jQuery but want to learn React way. Searching the forums and Google, I created a working model. However, I sense that the code can be simplified or at least audited for issues. Hoping for some senior enlightenment, all help is welcomed. Worried about dependency warnings and calling of 2 handlers. Did not use addEventListener as I read Safari may have issues.
Notes, I avoided using css media query for auto-hide, as I could not figure out how to override css file media query of display:none
from React when click component is used. Using react 17.0.2; bootstrap 5.1.3;
Dashboard.js
import { useEffect, useRef, useState } from 'react';
import "./Dashboard.css";
import Navbar from "./Navbar";
import Sidebar from "./Sidebar";
const Dashboard = () => {
const sidebarRef = useRef(null); // used to get sidebar width
const [usMobile, setMobile] = useState("");
const mq = window.matchmedia("(max-width: 498px)");
const [firsTime, setFirsTime] = useState(true);
useEffect(() => {
//handle sidebar display clicks from Navbar
// makes/sets initial sidebar state to open
// returns true when window is < 498px
// unmount cleanup handler
toggleSidebar();
mq.addListener(toggleSidebar);
return () => mq.removeListener(toggleSidebar);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // warning about dependency here
useEffect(() => {
// handles sidebar display based on resize
// returns treu when window is < 498
mq.addListener(hideSideBar);
return () => mq.removeListener(hideSideBar);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // warning about dependency here
const hideSideBar = () => {
if(mq.matches) {
setMobile(true);
} else {
setMobile(false);
}
}
// toggle sidebar based on user clicks
const toggleSidebar = ( clicked ) => {
let sidebarWidth = sidebarRef.current.offsetWidth;
if (firsTime) {
setShowSidebar(true);
setMobile(false);
setFirstTime(false);
} else {
if ( clicked = "yes") {
if (sidebarWidth == 0){
setShowSidebar(true);
setMobile(false);
} else {
setShowSidebar(false);
}
clicked = "";
}
}
}
return (
<span ref={sidebarRef}>
{!isMobile && showSidebar && <Sidebar onClick={toggleSidebar} />}
</span>
<div className="flex-fill content-wrapper">
<Navbar showSideBar={showSidebar} onClick={toggleSidebar} />
</div>
);
}
export default Dashboard;
Navbar.js
import {useState} from 'react';
const Navbar = (props) => {
// eslint-disable-next-line no-unused-vars
// set is unused but it will not work otherwise
const [clicked, setClicked] = useState("yes");
return (
<div className="d-flex justify-content-between bg-white py-2 ps-3 pe-4">
<div className="col-auto">
<button
className="menu-icon-btn"
onClick={() => props.onClick(clicked)} >
</button>
</div>
</div>
)
}
export default Navbar;
Sidebar.js
import {useState} from 'react';
const Sidebar = (props) => {
const [clicked, setClicked] = useState('yes');
return (
<div className="d-flex flex-column flex-shrink-0 text-white bg-dark sidebar">
<span className="fs4">Sidebar Title</span>
<ul className="nav nav-pills flex-column mb-auto">
<li className="nav-item">
Home
</li>
</ul>
<hr>
<span id="toggle-x"
className="btn btn-outline-primary border-0 inline d-md-none mx-auto mb-2">
aria-label="Toggle Sidebar Nav"
onClick={() => props.onClick(clicked)} >
CLOSE
</span>
</div>
)
}
export default Sidebar;
Dashboard.css
Most all css is from bootstrap; these are just app custom styles
.menu-icon-btn {
background: none;
border: none;
padding: 0;
}
.toggle-icon {
width: 32px;
height: 32px;
fill: var(--medium-gray);
cursor: pointer;
}
.menu-icon {
width: 20px;
height: 20px;
fill: var(--medium-gray);
cursor: pointer;
}
.sidebar,
.content-wrapper {
height: 100vh;
}
.sidebar-wrapper {
width: 200px;
}
/* Omitted the following as I was unable to force it to change from within React. In jQuery(show/hide seem to override this just fine.)*/
/* @media screen and (min-width: 0px) and (max-width: 700px) {
.sidebar-wrapper {
display: none;
}
} */
New Dashboard.js
const Dashboard = () => {
const isDesktop = () => window.innerWidth > 598;
const [sidebarStatus, setSidebarStatus] = useState("");
useEffect(() => {
window.addEventListener("resize", () => {
setSidebarStatus(isDesktop());
});
return () => window.removeEventListener("resize", isDesktop);
}, []);
const toggleSidebar = (open) => {
setSidebarStatus(open);
};
return (
{sidebarStatus && (
<Sidebar showSideBar={sidebarStatus} onClick={toggleSidebar} />
)}
)
}
New Navbar.js
const Navbar = (props) => {
return (
<button
id="toggle"
className="menu-icon-btn py-2"
data-menu-icon-btn
onClick={() => props.onClick(!props.showSideBar)}
> Toggle Sidebar
</button>
)
}
Your code is broken.. i made a little change to your code:
import React, { useEffect, useRef, useState } from "react";
import "./Dashboard.css";
import Navbar from "./Navbar";
import Sidebar from "./Sidebar";
const Dashboard = () => {
// i don't know what is using for..
const sidebarRef = useRef(null); // used to get sidebar width
const [isMobile, setMobile] = useState(document.body.clientWidth <= 498);
// use 'init' | 'open' | 'close', that you don't need remember if suer clicked
const [sidebarStatus, setSidebarStatus] = useState("init");
useEffect(() => {
// add listener only once, or many listeners would be created every render
const mq = window.matchMedia("(max-width: 498px)");
mq.addListener((res) => {
setMobile(res.matches);
});
return () => mq.removeListener(toggleSidebar);
}, []);
// react use status change fire effects, fire function manually is tired
// here calculate should show sidebar
const showSidebar =
sidebarStatus === "open" || (!isMobile && sidebarStatus === "init");
const toggleSidebar = (open) => {
setSidebarStatus(open ? "open" : "close");
};
return (
<>
<span ref={sidebarRef}>
{showSidebar && <Sidebar onClick={toggleSidebar} />}
</span>
<div className="flex-fill content-wrapper">
<Navbar showSideBar={showSidebar} onClick={toggleSidebar} />
</div>
</>
);
};
export default Dashboard;
attention that toggleSidebar param from navbar and sidebar are const true
and false
. Do not use status in different file