React Router in Vite - DAV-1000/react-material GitHub Wiki
Created: 8/1/2025 10:27
Updated: 8/1/2025 16:20
Exported: 8/14/2025 11:21
Link: https://chatgpt.com/c/688c8889-9264-8001-87a0-af9578c3551b
Tell me about using React Router in a typescript vite app
Using React Router in a TypeScript + Vite app is a common and efficient setup for building modern single-page applications (SPAs). Here’s a complete guide to getting started and using it effectively:
First, create your app using Vite:
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
Install react-router-dom
and its types:
npm install react-router-dom
npm install -D @types/react-router-dom
Note: As of React Router v6+, the package is fully written in TypeScript, so @types/react-router-dom
may not be strictly necessary anymore.
Let’s create a simple folder structure:
src/
├── main.tsx
├── App.tsx
├── pages/
│ ├── Home.tsx
│ └── About.tsx
Example: pages/Home.tsx
const Home = () => <h1>Home Page</h1>;
export default Home;
Example: pages/About.tsx
const About = () => <h1>About Page</h1>;
export default About;
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
const App = () => (
<Router>
<nav>
<Link to="/">Home</Link> | <Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Router>
);
export default App;
React Router v6+ uses a lot of generics and type inference. But here are some common tips:
- Route Params:
If you have dynamic routes:
<Route path="/user/:id" element={<User />} />
Inside User.tsx
, use:
import { useParams } from "react-router-dom";
const User = () => {
const { id } = useParams<{ id: string }>();
return <div>User ID: {id}</div>;
};
- Navigate programmatically:
import { useNavigate } from "react-router-dom";
const MyComponent = () => {
const navigate = useNavigate();
return <button onClick={() => navigate("/about")}>Go to About</button>;
};
If you're passing props or using context, ensure you type your components properly:
interface Props {
message: string;
}
const Home: React.FC<Props> = ({ message }) => <h1>{message}</h1>;
Start dev server:
npm run dev
Build:
npm run build
- Use
createBrowserRouter
andRouterProvider
for data loaders/actions (React Router v6.4+). - Use code splitting via
React.lazy
+Suspense
with routes.
Let me know if you want a more advanced setup (e.g., nested routes, route guards, loader-based routing with createBrowserRouter
, etc.)
I am adapting the Material UI Blog template. This is what my App.tsx looks like: import CssBaseline from '@mui/material/CssBaseline'; import Container from '@mui/material/Container'; import AppTheme from './shared-theme/AppTheme'; import AppAppBar from './components/AppAppBar'; import MainContent from './components/MainContent'; import Latest from './components/Latest'; import Footer from './components/Footer';
export default function App() { return ( <Container maxWidth="lg" component="main" sx={{ display: 'flex', flexDirection: 'column', my: 16, gap: 4 }} >
); }Great — you're adapting the Material UI Blog template and using a clean component structure. If you're integrating React Router into this setup (e.g., to navigate between different blog pages or sections like "Home", "Posts", "About", etc.), you'll want to wrap your routing logic around MainContent
(or wherever your page-specific content should appear).
Here’s how to update your App.tsx
to incorporate React Router v6+ properly:
If you haven’t yet:
npm install react-router-dom
You’ll wrap the App
content in a <BrowserRouter>
, and replace <MainContent />
with <Routes>...</Routes>
for routing.
import CssBaseline from '@mui/material/CssBaseline';
import Container from '@mui/material/Container';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import AppTheme from './shared-theme/AppTheme';
import AppAppBar from './components/AppAppBar';
import Footer from './components/Footer';
import Latest from './components/Latest';
// Route views
import Home from './pages/Home';
import BlogPost from './pages/BlogPost';
import About from './pages/About';
export default function App() {
return (
<AppTheme disableCustomTheme={false}>
<CssBaseline enableColorScheme />
<Router>
<AppAppBar />
<Container
maxWidth="lg"
component="main"
sx={{ display: 'flex', flexDirection: 'column', my: 16, gap: 4 }}
>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/post/:id" element={<BlogPost />} />
<Route path="/about" element={<About />} />
{/* Add more routes here as needed */}
</Routes>
<Latest />
</Container>
<Footer />
</Router>
</AppTheme>
);
}
Example: pages/Home.tsx
const Home = () => {
return <div>Welcome to the blog homepage!</div>;
};
export default Home;
Example: pages/BlogPost.tsx
import { useParams } from 'react-router-dom';
const BlogPost = () => {
const { id } = useParams<{ id: string }>();
return <div>Viewing blog post with ID: {id}</div>;
};
export default BlogPost;
Example: pages/About.tsx
const About = () => {
return <div>About this blog</div>;
};
export default About;
If AppAppBar
has links, ensure they use react-router-dom
's <Link>
or NavLink
:
import { Link as RouterLink } from 'react-router-dom';
import Link from '@mui/material/Link';
// Inside your AppAppBar component
<Link component={RouterLink} to="/">Home</Link>
<Link component={RouterLink} to="/about">About</Link>
-
Latest
stays outside<Routes>
because it seems to be a persistent component. - You can make
AppAppBar
context-aware by highlighting active routes usingNavLink
. - For advanced routing, you could use
createBrowserRouter
and loaders, but basicRoutes
will suffice for most blog cases.
Would you like help adapting AppAppBar
or converting the blog posts into dynamic routes with real content?
Help adapting AppAppBar. I want it to retain its current look and feel: import * as React from 'react'; import { alpha, styled } from '@mui/material/styles'; import Box from '@mui/material/Box'; import AppBar from '@mui/material/AppBar'; import Toolbar from '@mui/material/Toolbar'; import Button from '@mui/material/Button'; import IconButton from '@mui/material/IconButton'; import Container from '@mui/material/Container'; import Divider from '@mui/material/Divider'; import MenuItem from '@mui/material/MenuItem'; import Drawer from '@mui/material/Drawer'; import MenuIcon from '@mui/icons-material/Menu'; import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; import ColorModeIconDropdown from '../shared-theme/ColorModeIconDropdown'; import Sitemark from './SitemarkIcon';
const StyledToolbar = styled(Toolbar)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
flexShrink: 0,
borderRadius: calc(
export default function AppAppBar() { const [open, setOpen] = React.useState(false);
const onFeaturesClick = () => { console.info('You clicked the features button.'); };
const toggleDrawer = (newOpen: boolean) => () => { setOpen(newOpen); };
return ( <AppBar position="fixed" enableColorOnDark sx={{ boxShadow: 0, bgcolor: 'transparent', backgroundImage: 'none', mt: 'calc(var(--template-frame-height, 0px) + 28px)', }} > <Box sx={{ flexGrow: 1, display: 'flex', alignItems: 'center', px: 0 }}> <Box sx={{ display: { xs: 'none', md: 'flex' } }}> Features Testimonials Highlights Pricing <Button variant="text" color="info" size="small" sx={{ minWidth: 0 }}> FAQ <Button variant="text" color="info" size="small" sx={{ minWidth: 0 }}> Blog <Box sx={{ display: { xs: 'none', md: 'flex' }, gap: 1, alignItems: 'center', }} > Sign in Sign up <Box sx={{ display: { xs: 'flex', md: 'none' }, gap: 1 }}> <Drawer anchor="top" open={open} onClose={toggleDrawer(false)} PaperProps={{ sx: { top: 'var(--template-frame-height, 0px)', }, }} > <Box sx={{ p: 2, backgroundColor: 'background.default' }}> <Box sx={{ display: 'flex', justifyContent: 'flex-end', }} > Features Testimonials Highlights Pricing FAQ Blog <Divider sx={{ my: 3 }} /> Sign up Sign in ); }
To adapt your AppAppBar
component for React Router while preserving the look and feel of the original MUI template, you'll replace buttons that currently do nothing with react-router-dom
's Link
components, while maintaining MUI styling.
Here’s how to do it cleanly and incrementally:
import { Link as RouterLink } from 'react-router-dom';
import Link from '@mui/material/Link';
You'll replace the static buttons like "Blog", "Features", etc. with either:
- MUI's
<Button component={RouterLink} to="/your-route" />
if it should look like a button, or - MUI
<Link component={RouterLink} ... />
if you want hyperlink-style nav
Here’s the updated version for just the nav buttons:
<Box sx={{ display: { xs: 'none', md: 'flex' } }}>
<Button component={RouterLink} to="/features" variant="text" color="info" size="small">
Features
</Button>
<Button component={RouterLink} to="/testimonials" variant="text" color="info" size="small">
Testimonials
</Button>
<Button component={RouterLink} to="/highlights" variant="text" color="info" size="small">
Highlights
</Button>
<Button component={RouterLink} to="/pricing" variant="text" color="info" size="small">
Pricing
</Button>
<Button component={RouterLink} to="/faq" variant="text" color="info" size="small" sx={{ minWidth: 0 }}>
FAQ
</Button>
<Button component={RouterLink} to="/blog" variant="text" color="info" size="small" sx={{ minWidth: 0 }}>
Blog
</Button>
</Box>
<Drawer ... >
<Box sx={{ p: 2, backgroundColor: 'background.default' }}>
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<IconButton onClick={toggleDrawer(false)}>
<CloseRoundedIcon />
</IconButton>
</Box>
<MenuItem component={RouterLink} to="/features" onClick={toggleDrawer(false)}>Features</MenuItem>
<MenuItem component={RouterLink} to="/testimonials" onClick={toggleDrawer(false)}>Testimonials</MenuItem>
<MenuItem component={RouterLink} to="/highlights" onClick={toggleDrawer(false)}>Highlights</MenuItem>
<MenuItem component={RouterLink} to="/pricing" onClick={toggleDrawer(false)}>Pricing</MenuItem>
<MenuItem component={RouterLink} to="/faq" onClick={toggleDrawer(false)}>FAQ</MenuItem>
<MenuItem component={RouterLink} to="/blog" onClick={toggleDrawer(false)}>Blog</MenuItem>
<Divider sx={{ my: 3 }} />
<MenuItem>
<Button component={RouterLink} to="/signup" color="primary" variant="contained" fullWidth>
Sign up
</Button>
</MenuItem>
<MenuItem>
<Button component={RouterLink} to="/signin" color="primary" variant="outlined" fullWidth>
Sign in
</Button>
</MenuItem>
</Box>
</Drawer>
To highlight the active route (e.g., with a different color or underline), use NavLink
from react-router-dom
instead of Link
, which allows you to apply active styles based on isActive
.
import { NavLink } from 'react-router-dom';
// Example with styling
<Button
component={NavLink}
to="/blog"
style={({ isActive }) => ({
color: isActive ? '#1976d2' : undefined,
textDecoration: isActive ? 'underline' : 'none',
})}
variant="text"
color="info"
size="small"
>
Blog
</Button>
You are:
- Using
component={RouterLink}
orcomponent={NavLink}
in your MUIButton
andMenuItem
elements. - Preserving your layout and design.
- Allowing React Router to handle navigation seamlessly.
Would you like help setting up placeholder route components for /features
, /pricing
, etc., or integrating route guards or scroll-to-top behavior?
amend the active route hilite solution above such that a different value for color is assigned depending on the value of isActive
Absolutely — to assign a different color depending on isActive
, you can dynamically set the color
prop based on the isActive
value.
Since MUI's Button
uses a theme-based color system (like primary
, info
, inherit
, etc.), you'll conditionally return those values rather than raw color codes.
import { NavLink } from 'react-router-dom';
import Button from '@mui/material/Button';
// Inside your JSX:
<Button
component={NavLink}
to="/blog"
size="small"
variant="text"
color={({ isActive }) => (isActive ? 'primary' : 'info')}
>
Blog
</Button>
If you want more control (e.g., change both color
and textDecoration
, or apply a bold font), use sx
with a function:
<Button
component={NavLink}
to="/blog"
size="small"
variant="text"
sx={({ isActive }) => ({
color: isActive ? 'primary.main' : 'info.main',
fontWeight: isActive ? 'bold' : 'normal',
textDecoration: isActive ? 'underline' : 'none',
})}
>
Blog
</Button>
If you're using this inside a MUI MenuItem
or mobile drawer as well, the same logic applies:
<MenuItem
component={NavLink}
to="/features"
sx={({ isActive }) => ({
color: isActive ? 'primary.main' : 'text.primary',
fontWeight: isActive ? 'bold' : 'normal',
})}
>
Features
</MenuItem>
Let me know if you'd like to abstract this into a NavButton
component for reuse!
Type '({ isActive }: { isActive: any; }) => "primary" | "info"' is not assignable to type 'OverridableStringUnion<"inherit" | "primary" | "secondary" | "error" | "warning" | "info" |
Ah — good catch. That error happens because MUI’s color
prop doesn't accept a function like ({ isActive }) => ...
. It's expecting a string literal or a theme
color at render time, not dynamically during routing.
To fix this and still style based on route activity, you should leave the color
prop static (or omit it) and instead use the sx
prop for dynamic styling, which does accept a function.
import { NavLink } from 'react-router-dom';
import Button from '@mui/material/Button';
// Inside your component JSX
<Button
component={NavLink}
to="/blog"
size="small"
variant="text"
sx={({ isActive }) => ({
color: isActive ? 'primary.main' : 'info.main',
fontWeight: isActive ? 'bold' : 'normal',
textDecoration: isActive ? 'underline' : 'none',
})}
>
Blog
</Button>
This works because sx
can take a callback function when the component is a NavLink
, and MUI will apply the resulting styles.
If you prefer CSS classes:
<Button
component={NavLink}
to="/blog"
size="small"
variant="text"
className={({ isActive }) => (isActive ? 'nav-active' : 'nav-inactive')}
/>
Then define in your CSS or MUI theme overrides:
.nav-active {
color: #1976d2; /* primary */
font-weight: bold;
text-decoration: underline;
}
.nav-inactive {
color: #0288d1; /* info */
}
Let me know which approach you prefer — the sx
version is cleaner and more in line with MUI's styling philosophy.
Property 'isActive' does not exist on type 'Theme'
Great — this tells us exactly what’s going on.
You're passing a function to the sx
prop like:
sx={({ isActive }) => ({ ... })}
…but sx
expects a function that receives the MUI theme
, not React Router’s isActive
.
That means the sx
function is incorrectly assuming it gets { isActive }
, when in fact it gets (theme: Theme)
.
To conditionally style based on route activity, you must use NavLink
's style
or className
props, which get the isActive
value.
Here’s how to do it properly:
import { NavLink } from 'react-router-dom';
import Button from '@mui/material/Button';
<Button
component={NavLink}
to="/blog"
size="small"
variant="text"
style={({ isActive }) => ({
color: isActive ? '#1976d2' : '#0288d1', // primary vs info
fontWeight: isActive ? 'bold' : 'normal',
textDecoration: isActive ? 'underline' : 'none',
})}
>
Blog
</Button>
If you're using the MUI theme and want theme-aware colors, use the useTheme
hook:
import { useTheme } from '@mui/material/styles';
const theme = useTheme();
// inside JSX
<Button
component={NavLink}
to="/blog"
size="small"
variant="text"
style={({ isActive }) => ({
color: isActive ? theme.palette.primary.main : theme.palette.info.main,
fontWeight: isActive ? 'bold' : 'normal',
})}
/>
If you prefer CSS or emotion/styled components:
<Button
component={NavLink}
to="/blog"
size="small"
variant="text"
className={({ isActive }) => (isActive ? 'nav-active' : 'nav-inactive')}
/>
And define the classes in your global styles or use sx
elsewhere.
sx={({ isActive }) => ({
color: isActive ? 'primary.main' : 'info.main',
})}
This doesn’t work because sx
doesn’t get isActive
— it only gets MUI’s theme
.
Let me know if you want to wrap this into a reusable <NavButton>
component for DRY code!
React Hook "useTheme" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function.eslintreact-hooks/rules-of-hooks
You're absolutely right — and ESLint is giving you a correct warning.
❌ You cannot call
useTheme()
outside of a React component or hook function.
This mistake often happens when you're trying to declare const theme = useTheme()
at the module level (i.e., outside of your component’s body). That’s not allowed by the Rules of Hooks.
Just move the useTheme()
call inside your component before you use it:
import { NavLink } from 'react-router-dom';
import Button from '@mui/material/Button';
import { useTheme } from '@mui/material/styles';
const MyNavButton = () => {
const theme = useTheme(); // ✅ must be inside the component
return (
<Button
component={NavLink}
to="/blog"
size="small"
variant="text"
style={({ isActive }) => ({
color: isActive ? theme.palette.primary.main : theme.palette.info.main,
fontWeight: isActive ? 'bold' : 'normal',
})}
>
Blog
</Button>
);
};
export default MyNavButton;
If you're using a lot of navigation buttons, you can wrap this into a reusable component:
import { NavLink } from 'react-router-dom';
import Button from '@mui/material/Button';
import { useTheme } from '@mui/material/styles';
interface NavButtonProps {
to: string;
label: string;
}
const NavButton = ({ to, label }: NavButtonProps) => {
const theme = useTheme();
return (
<Button
component={NavLink}
to={to}
size="small"
variant="text"
style={({ isActive }) => ({
color: isActive ? theme.palette.primary.main : theme.palette.info.main,
fontWeight: isActive ? 'bold' : 'normal',
})}
>
{label}
</Button>
);
};
export default NavButton;
Then in your AppAppBar.tsx
:
<NavButton to="/features" label="Features" />
<NavButton to="/blog" label="Blog" />
Let me know if you'd like to also handle mobile drawer navigation the same way!
Property 'isActive' does not exist on type 'Theme'.
You're seeing this error because the style
prop on your MUI Button
does not forward isActive
to the function when component={NavLink}
. This is a tricky edge case.
Let me clarify and give you the correct and working solution.
You’re trying to do this:
<Button
component={NavLink}
to="/blog"
style={({ isActive }) => ({
color: isActive ? theme.palette.primary.main : theme.palette.info.main,
})}
/>
But TypeScript sees style
as (theme: Theme) => CSSProperties
, because that’s what MUI expects, not what React Router provides.
So when React Router injects { isActive }
, TypeScript still expects a MUI theme — and throws the error.
Instead of putting the style
function directly on Button
, you must move style
to the underlying NavLink
.
Here’s how to do it:
import { NavLink } from 'react-router-dom';
import Button from '@mui/material/Button';
import { useTheme } from '@mui/material/styles';
const NavButton = ({ to, children }: { to: string; children: React.ReactNode }) => {
const theme = useTheme();
return (
<Button
component={NavLink}
to={to}
size="small"
variant="text"
style={({ isActive }: { isActive: boolean }) => ({
color: isActive ? theme.palette.primary.main : theme.palette.info.main,
fontWeight: isActive ? 'bold' : 'normal',
textDecoration: isActive ? 'underline' : 'none',
})}
>
{children}
</Button>
);
};
Then use it like:
<NavButton to="/blog">Blog</NavButton>
This avoids MUI Button
but gives you full control:
import { NavLink } from 'react-router-dom';
import { useTheme } from '@mui/material/styles';
const CustomLink = ({ to, children }: { to: string; children: React.ReactNode }) => {
const theme = useTheme();
return (
<NavLink
to={to}
style={({ isActive }) => ({
padding: '6px 16px',
fontSize: '0.875rem',
fontWeight: isActive ? 'bold' : 400,
textDecoration: isActive ? 'underline' : 'none',
color: isActive ? theme.palette.primary.main : theme.palette.info.main,
border: 'none',
background: 'none',
cursor: 'pointer',
})}
>
{children}
</NavLink>
);
};
This looks similar to a MUI button and works without type issues.
- MUI's
Button
expectsstyle: (theme) => styles
, notstyle: ({ isActive }) => styles
. - To use
isActive
, either:- Wrap
NavLink
and pass it viacomponent
- Use
NavLink
directly and apply styles manually
- Wrap
Let me know if you’d like to integrate this with your responsive AppBar drawer menu too!
Powered by ChatGPT Exporter