This post walks through setting up reusable UI components and active navigation in a React app using React Router’s NavLink, preparing the codebase for authentication and future feature pages.
Click here for Act 2 · Scene 2
Table of Contents
- Overview
- Creating a Reusable Card Component
- Adding Active Navigation with NavLink
- What Happens When Routes Don’t Exist (Yet)
- Mental Model
- Why This Scene Matters
Act 3 · Scene 1: Before The AuthPage
In this scene, I introduced reusable UI building blocks and active navigation using React Router’s NavLink, laying groundwork for authentication and analysis flows before building the actual pages.
Overview
With routing, layout, and the home page now stable, the application finally has a backbone. At this point, it made sense to pause feature work and ask myself a quiet question:
What UI decision am I going to regret not making early?
Two answers came back immediately:
- Reusable layout wrappers
- Navigation that knows where you are
So that’s what this scene is about.
Here in this scene, I am introducing two structural UI elements that will support everything that comes next:
- A reusable Card component to standardize layout
- Active navigation state using NavLink
Creating a Reusable Card Component
A Card is a reusable UI wrapper component that standardizes layout and styling. It centralizes layout and styling ensuring a consistent visual structure across the application.
Instead of repeating padding, borders, and spacing rules, I created a reusable wrapper.
Folder structure:
components/
ui/
card/
Card.tsx
Card.module.scss
The Card.tsx file:
import type React from "react";
import classes from "./Card.module.scss";
const Card: React.FC<{ children: React.ReactNode; className: string }> = (
props
) => {
return (
<div className={`${classes.card} ${props.className}`}>
{props.children}
</div>
);
};
export default Card;
Just a wrapper that does its job and stays out of the way. This is the kind of component you don’t think about later.
Adding Active Navigation with NavLink
Next problem:
The navigation had no awareness. It didn’t know where the user was. It didn’t react.
That felt kind of off.
I needed it to react when a user visits a specific page or route.
Instead of using the ever popular Link, I used NavLink in its place. And added a small helper to conditionally apply styles based on route activity:
const navDataHandler = (navData: any) => {
return navData.isActive ? classes.active : "";
};
The MainNav looked like this:
<h1>
<NavLink to="/" className={navDataHandler}>
<span>AI Talent Profile</span> Analyzer
</NavLink>
</h1>
<nav>
<ul>
<li>
<NavLink to="/auth" className={navDataHandler}>
Auth
</NavLink>
</li>
<li>
<NavLink to="/analysis-form" className={navDataHandler}>
Analyze Candidate
</NavLink>
</li>
</ul>
</nav>
This was added to the scss file:
.active {
border-bottom: 1px solid red;
}
a {
text-decoration: none;
}
At this point, active routes were visually clear.
What Happens When Routes Don’t Exist (Yet)
Alright, here comes an interesting part.
I saved the file. Hot reload kicked in. And for a moment, everything looked… fine.
The active state kicked in exactly where it should; there was a red underline under AI Talent Profile Analyzer, which told me the NavLink logic was doing its job.
But then I clicked Auth. And Boooooooom!!!
I saw this:
Got the Same thing for Analyze Candidate.
Meanwhile I saw this in the console for both auth and analyze candidate:
For auth:
For AnalyzeCandidate:
No routes matched location is what is being displayed in the browser console for both auth and analyze candidate.
But in all honesty, this was expected.
Do you know why?
This happened because these routes don’t exist yet in the router configuration.
Current router tree in App.tsx:
function App() {
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
errorElement: <ErrorPage />,
children: [{ path: "/", element: <HomePage /> }],
},
]);
return <RouterProvider router={router} />;
}
As you can see from the code above, there are no entries for: /auth or /analysis-form.
So React Router correctly falls back to the error boundary.
This is not a bug. I repeat, this is not a bug.
That’s the system working as designed.
So, how do I go about fixing it? Well, stay tuned. We will break it down in the next scene.
Mental Model
At this stage:
- Navigation reflects application state
- UI building blocks are reusable and consistent
- Routes are intentionally incomplete
- The app is telling me what to build next
Why This Scene Matters
This scene shows that I:
- Build foundations before features
- Expect systems to be incomplete mid-build
- Think in reusable UI layers
- Let architecture guide the next step
Thanks for reading.
Let’s move on to the Next Scene.
We in the Building…
Building in Progress…




Top comments (0)