Step 10: Adding Routing#
** Quick Tip: Each step has two parts. Part 1 shows you what to build. Part 2** explains why it works. Want to just build? Skip all Part 2 sections!
In this step, you'll add multiple pages to your app so users can navigate between login, signup, and todos!
Part 1: Building the App#
Step 10.1: Import Routing Components#
Update your imports:
cl import from react {useState, useEffect}
cl import from "@jac-client/utils" {
Router,
Routes,
Route,
Link,
Navigate,
useNavigate,
jacSignup,
jacLogin,
jacLogout,
jacIsLoggedIn
}
cl {
# ... your components
}
Step 10.2: Add Navigation Links to Login Page#
Update your LoginPage to include a link to signup:
# In LoginPage, replace the last <p> tag with:
<p style={{
"textAlign": "center",
"marginTop": "12px",
"fontSize": "14px"
}}>
Need an account? <Link to="/signup">Sign up</Link>
</p>
Step 10.3: Add Navigation Links to Signup Page#
Update your SignupPage:
# In SignupPage, replace the last <p> tag with:
<p style={{
"textAlign": "center",
"marginTop": "12px",
"fontSize": "14px"
}}>
Have an account? <Link to="/login">Login</Link>
</p>
Step 10.4: Add Navigation After Login/Signup#
Update the login handler to navigate to todos after successful login:
# In LoginPage
async def handleLogin(e: any) -> None {
e.preventDefault();
setError("");
if not username or not password {
setError("Please fill in all fields");
return;
}
success = await jacLogin(username, password);
if success {
window.location.href = "/page/app#/todos"; # Navigate to todos
} else {
setError("Invalid credentials");
}
}
Update the signup handler:
# In SignupPage
async def handleSignup(e: any) -> None {
e.preventDefault();
setError("");
if not username or not password {
setError("Please fill in all fields");
return;
}
result = await jacSignup(username, password);
if result["success"] {
window.location.href = "/page/app#/todos"; # Navigate to todos
} else {
setError(result["error"] if result["error"] else "Signup failed");
}
}
Step 10.5: Create a Navigation Component#
Add this component to show navigation at the top:
def Navigation() -> any {
let isLoggedIn = jacIsLoggedIn();
if isLoggedIn {
return <nav style={{
"padding": "12px 24px",
"background": "#3b82f6",
"color": "#ffffff",
"display": "flex",
"justifyContent": "space-between"
}}>
<div style={{"fontWeight": "600"}}>Todo App</div>
<div style={{"display": "flex", "gap": "16px"}}>
<Link to="/todos" style={{
"color": "#ffffff",
"textDecoration": "none"
}}>
Todos
</Link>
<button
onClick={lambda e: any -> None {
e.preventDefault();
jacLogout();
window.location.href = "/page/app#/login";
}}
style={{
"background": "none",
"color": "#ffffff",
"border": "1px solid #ffffff",
"padding": "2px 10px",
"borderRadius": "4px",
"cursor": "pointer"
}}
>
Logout
</button>
</div>
</nav>;
}
return <nav style={{
"padding": "12px 24px",
"background": "#3b82f6",
"color": "#ffffff",
"display": "flex",
"justifyContent": "space-between"
}}>
<div style={{"fontWeight": "600"}}>Todo App</div>
<div style={{"display": "flex", "gap": "16px"}}>
<Link to="/login" style={{
"color": "#ffffff",
"textDecoration": "none"
}}>
Login
</Link>
<Link to="/signup" style={{
"color": "#ffffff",
"textDecoration": "none"
}}>
Sign Up
</Link>
</div>
</nav>;
}
Step 10.6: Update TodosPage to Redirect if Not Logged In#
Update your TodosPage:
def TodosPage() -> any {
# Redirect to login if not logged in
if not jacIsLoggedIn() {
return <Navigate to="/login" />;
}
# ... all your existing todo code
}
Step 10.7: Create a Home Page#
Add this simple home page:
def HomePage() -> any {
if jacIsLoggedIn() {
return <Navigate to="/todos" />;
}
return <Navigate to="/login" />;
}
Step 10.8: Set Up Router in app()#
Now, update your app function to use the router:
def app() -> any {
return <Router>
<div style={{"fontFamily": "system-ui, sans-serif"}}>
<Navigation />
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/signup" element={<SignupPage />} />
<Route path="/todos" element={<TodosPage />} />
</Routes>
</div>
</Router>;
}
Try it!
- Go to
http://localhost:8000/page/app- you'll be redirected to login - Click "Sign up" - goes to signup page
- Create an account - redirects to todos
- Click "Logout" - redirects to login
- Try manually going to
/page/app#/todoswhile logged out - redirects to login!
⏭ Want to skip the theory? Jump to Step 11: Final Integration
Part 2: Understanding the Concepts#
What is Routing?#
Routing = Different URLs show different pages
Traditional websites:
- /login → Server sends login.html
- /todos → Server sends todos.html
- Every click = full page reload
Single-Page Apps (SPAs):
- /login → JavaScript shows login component
- /todos → JavaScript shows todos component
- No page reload = instant and smooth!
Router Components#
<Router> # Container for all routing
<Routes> # Groups route definitions
<Route /> # Defines one route
</Routes>
</Router>
Think of it like:
# Python routing (Flask)
@app.route("/login")
def login():
return render_template("login.html")
# Jac routing
<Route path="/login" element={<LoginPage />} />
The Router Setup#
<Router>
<div>
<Navigation /> # Shows on all pages
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
</Routes>
</div>
</Router>
What happens:
- Router manages the current URL
- Navigation is always visible
- Routes shows ONE matching route
- Components render based on URL
Route Definitions#
Breakdown:
- path="/login" - URL pattern to match
- element={<LoginPage />} - Component to show (must be JSX!)
Important: Use element={<Component />} not element={Component}
Link Component#
vs regular anchor tag:
# Wrong - causes page reload
<a href="/login">Go to Login</a>
# Correct - no page reload
<Link to="/login">Go to Login</Link>
Link updates the URL without reloading the page!
Navigate Component#
def TodosPage() -> any {
if not jacIsLoggedIn() {
return <Navigate to="/login" />;
}
// Show todos
}
Purpose: Redirect programmatically
When to use: - Protecting routes (if not logged in, redirect) - After form submission - Conditional redirects
useNavigate Hook#
let navigate = useNavigate();
async def handleLogin() -> None {
let success = await jacLogin(username, password);
if success {
navigate("/todos"); # Navigate programmatically
}
}
Use when: You need to navigate from JavaScript code (not from JSX)
URL Structure in Jac#
/page/app- Your Jac app#/todos- Client-side route (hash routing)
Protected Routes Pattern#
def ProtectedPage() -> any {
if not jacIsLoggedIn() {
return <Navigate to="/login" />;
}
# User is authenticated, show page
return <div>Protected content</div>;
}
This pattern: 1. Checks authentication 2. Redirects if not logged in 3. Shows content if logged in
Conditional Navigation#
def Navigation() -> any {
let isLoggedIn = jacIsLoggedIn();
if isLoggedIn {
return <nav>
<Link to="/todos">Todos</Link>
<button onClick={logout}>Logout</button>
</nav>;
}
return <nav>
<Link to="/login">Login</Link>
<Link to="/signup">Sign Up</Link>
</nav>;
}
Shows different links based on login status!
Route Matching#
Routes are matched in order:
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/todos" element={<TodosPage />} />
</Routes>
- URL
/→ ShowsHomePage - URL
/login→ ShowsLoginPage - URL
/todos→ ShowsTodosPage - URL
/other→ Shows nothing (we could add a 404 page)
Common Routing Patterns#
Pattern 1: Auto-redirect based on auth
def HomePage() -> any {
if jacIsLoggedIn() {
return <Navigate to="/todos" />;
}
return <Navigate to="/login" />;
}
Pattern 2: Logout and redirect
Pattern 3: Conditional links
{(
<Link to="/dashboard">Dashboard</Link>
) if jacIsLoggedIn() else (
<Link to="/login">Login</Link>
)}
What You've Learned#
- What client-side routing is
- Setting up Router, Routes, and Route
- Creating navigation with Link
- Programmatic navigation with Navigate
- Protected routes with authentication checks
- Conditional navigation based on auth status
- Common routing patterns
Common Issues#
Issue: Links don't work#
Check:
- Is everything wrapped in <Router>?
- Are you using <Link> not <a>?
- Is the to prop correct? to="/login" not href="/login"
Issue: Page reloads when clicking links#
Cause: Using <a href=""> instead of <Link to="">
Issue: Navigate not working#
Check:
- Is <Navigate> inside a component rendered by <Route>?
- Is it wrapped in <Router>?
Issue: Can't access protected page when logged in#
Check:
- Is jacIsLoggedIn() returning true?
- Did you successfully login?
- Check browser console for errors
Quick Exercise#
Try adding a 404 page for unknown routes:
def NotFoundPage() -> any {
return <div style={{
"textAlign": "center",
"padding": "50px"
}}>
<h1>404 - Page Not Found</h1>
<Link to="/">Go Home</Link>
</div>;
}
# Add as last route (catches everything else)
<Route path="*" element={<NotFoundPage />} />
Next Step#
Congratulations! You've learned all the key concepts. Now let's put everything together into the complete, final app!