I have a sidebar in a NextJS application and a main component placed in a row. The Sidebar has multiple items clicking on each item updates the main-content <Preview/>
by pushing a route (Updates the url), But this is rerendering both sidebar and main-content. My understanding is the components are incorrectly placed which is causing the issue. Right now I have placed both sidebar and main component inside the <Home/>
How should the folder structure and components structure be? Consider that Main component is a dynamic component (renders based on id passed to it).
This is the folder structure
project-root /
├──src
│ ├── app /
│ │ ├── models /
│ │ │ ├── Log.ts
│ │ ├── services /
│ │ │ ├── LogService.ts
│ │ ├── components /
│ │ │ ├── Sidebar.tsx
│ │ │ ├── Navbar.tsx
│ │ │ ├── Preview.tsx
│ │ │ └── Home.tsx
│ │ │ │
│ │ ├── logs /
│ │ │ ├──[id]
│ │ │ │ ├── page.tsx
│ │ └── layout.tsx
│ │ └── global.css
│ │ └── page.tsx
This is the dynamic page logs/[id]/page.tsx
// logs/[id]/page.tsx
import Home from '@/app/_components/Home';
import LogService from '../../_services/logService';
// This is required for dynamic routing in runtime
export const dynamicParams = true;
export async function generateStaticParams() {
const logService = new LogService();
const logs = await logService.fetchLogs();
return logs.map(log => ({ id: log.id }));
}
export default function Page({ params }: { params: { id: string } }) {
const { id } = params;
return <Home id={id} />
};
// _components/Home.tsx
"use client";
import IconButton from "./IconButton";
import PSNavbar from "./PSNavbar";
import Pastelog from "./Pastelog";
import Preview from './Preview';
import Sidebar from './Sidebar';
import { Theme } from './ThemeSwitcher';
export default function Home({ id }: { id: string | null }) {
const [showSideBar, setShowSideBar] = useState<boolean>(true);
const [logs, setLogs] = useState<Log[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [selectedLogId, setSelectedLogId] = useState<string | null>(id);
...
return (
<div className={`flex h-screen ${theme == Theme.DARK ? 'darkTheme' : 'lightTheme'}`}>
{
(showSideBar && <IconButton
onClick={() => setShowSideBar(!showSideBar)}>
<ViewSidebarRoundedIcon />
</IconButton>
)
}
<div>
{showSideBar && (
<Sidebar
loading={loading}
logs={logs}
id={selectedLogId}
onLogClick={(id) => {
if (id) {
router.push('/logs/$id');
setSelectedLogId(id!);
} else {
setSelectedLogId(null);
router.push('/');
}
}}
/>
)}
</div>
{/* Main content */}
<div className={`grow overflow-y-auto transition-all duration-800 ${showSideBar ? 'pl-64' : 'pl-0'}`}>
<div className="flex flex-col h-full">
<PSNavbar
sideBarIcon={!showSideBar}
onToggleSidebar={() => setShowSideBar(!showSideBar)}
/>
{
selectedLogId ? (<Preview
id={selectedLogId}
showNavbar={false}
/>) :
(<Pastelog />)
}
</div>
</div>
</div>
);
}
The issue with this approach is I would like to update the url when a list item is selected in the sidebar It works, but this is triggering rerendering of the entire home page (expected), but I would expect only main content to be rendered.
Also if a user visits the website with a link <baseurl>/sidebar-item
, I would expect the app to load with sidebar-item selected (This is working fine as of now)
Complete source code here
This is the output of the above code which shows the sidebar rerendering
If sidebar content is common for all the page then you can move sidebar component to layout file. for example:
export default function Layout ({ children } : { children : React.ReactNode}) {
return <>
<Sidebar />
{children} //page content will go here
</>
}