jac-client 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jac_client/docs/README.md +232 -172
- jac_client/docs/advanced-state.md +1012 -452
- jac_client/docs/asset-serving/intro.md +209 -0
- jac_client/docs/assets/pipe_line-v2.svg +32 -0
- jac_client/docs/assets/pipe_line.png +0 -0
- jac_client/docs/file-system/intro.md +90 -0
- jac_client/docs/guide-example/intro.md +117 -0
- jac_client/docs/guide-example/step-01-setup.md +260 -0
- jac_client/docs/guide-example/step-02-components.md +416 -0
- jac_client/docs/guide-example/step-03-styling.md +478 -0
- jac_client/docs/guide-example/step-04-todo-ui.md +477 -0
- jac_client/docs/guide-example/step-05-local-state.md +530 -0
- jac_client/docs/guide-example/step-06-events.md +750 -0
- jac_client/docs/guide-example/step-07-effects.md +469 -0
- jac_client/docs/guide-example/step-08-walkers.md +534 -0
- jac_client/docs/guide-example/step-09-authentication.md +586 -0
- jac_client/docs/guide-example/step-10-routing.md +540 -0
- jac_client/docs/guide-example/step-11-final.md +964 -0
- jac_client/docs/imports.md +538 -46
- jac_client/docs/lifecycle-hooks.md +517 -297
- jac_client/docs/routing.md +487 -357
- jac_client/docs/styling/intro.md +250 -0
- jac_client/docs/styling/js-styling.md +373 -0
- jac_client/docs/styling/material-ui.md +346 -0
- jac_client/docs/styling/pure-css.md +305 -0
- jac_client/docs/styling/sass.md +409 -0
- jac_client/docs/styling/styled-components.md +401 -0
- jac_client/docs/styling/tailwind.md +303 -0
- jac_client/examples/asset-serving/css-with-image/.babelrc +9 -0
- jac_client/examples/asset-serving/css-with-image/README.md +91 -0
- jac_client/examples/asset-serving/css-with-image/app.jac +67 -0
- jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
- jac_client/examples/asset-serving/css-with-image/package.json +28 -0
- jac_client/examples/asset-serving/css-with-image/styles.css +27 -0
- jac_client/examples/asset-serving/css-with-image/vite.config.js +29 -0
- jac_client/examples/asset-serving/image-asset/.babelrc +9 -0
- jac_client/examples/asset-serving/image-asset/README.md +119 -0
- jac_client/examples/asset-serving/image-asset/app.jac +43 -0
- jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
- jac_client/examples/asset-serving/image-asset/package.json +28 -0
- jac_client/examples/asset-serving/image-asset/styles.css +27 -0
- jac_client/examples/asset-serving/image-asset/vite.config.js +29 -0
- jac_client/examples/asset-serving/import-alias/.babelrc +9 -0
- jac_client/examples/asset-serving/import-alias/README.md +83 -0
- jac_client/examples/asset-serving/import-alias/app.jac +57 -0
- jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
- jac_client/examples/asset-serving/import-alias/package.json +28 -0
- jac_client/examples/asset-serving/import-alias/vite.config.js +29 -0
- jac_client/examples/basic/.babelrc +9 -0
- jac_client/examples/basic/README.md +16 -0
- jac_client/examples/basic/app.jac +16 -0
- jac_client/examples/basic/package.json +27 -0
- jac_client/examples/basic/vite.config.js +28 -0
- jac_client/examples/basic-auth/.babelrc +9 -0
- jac_client/examples/basic-auth/README.md +16 -0
- jac_client/examples/basic-auth/app.jac +308 -0
- jac_client/examples/basic-auth/package.json +27 -0
- jac_client/examples/basic-auth/vite.config.js +28 -0
- jac_client/examples/basic-auth-with-router/.babelrc +9 -0
- jac_client/examples/basic-auth-with-router/README.md +60 -0
- jac_client/examples/basic-auth-with-router/app.jac +464 -0
- jac_client/examples/basic-auth-with-router/package.json +28 -0
- jac_client/examples/basic-auth-with-router/vite.config.js +28 -0
- jac_client/examples/basic-full-stack/.babelrc +9 -0
- jac_client/examples/basic-full-stack/README.md +18 -0
- jac_client/examples/basic-full-stack/app.jac +320 -0
- jac_client/examples/basic-full-stack/package.json +28 -0
- jac_client/examples/basic-full-stack/vite.config.js +28 -0
- jac_client/examples/css-styling/js-styling/.babelrc +9 -0
- jac_client/examples/css-styling/js-styling/README.md +183 -0
- jac_client/examples/css-styling/js-styling/app.jac +63 -0
- jac_client/examples/css-styling/js-styling/package.json +28 -0
- jac_client/examples/css-styling/js-styling/styles.js +100 -0
- jac_client/examples/css-styling/js-styling/vite.config.js +28 -0
- jac_client/examples/css-styling/material-ui/.babelrc +9 -0
- jac_client/examples/css-styling/material-ui/README.md +16 -0
- jac_client/examples/css-styling/material-ui/app.jac +82 -0
- jac_client/examples/css-styling/material-ui/package.json +32 -0
- jac_client/examples/css-styling/material-ui/vite.config.js +28 -0
- jac_client/examples/css-styling/pure-css/.babelrc +9 -0
- jac_client/examples/css-styling/pure-css/README.md +16 -0
- jac_client/examples/css-styling/pure-css/app.jac +63 -0
- jac_client/examples/css-styling/pure-css/package.json +28 -0
- jac_client/examples/css-styling/pure-css/styles.css +112 -0
- jac_client/examples/css-styling/pure-css/vite.config.js +28 -0
- jac_client/examples/css-styling/sass-example/.babelrc +9 -0
- jac_client/examples/css-styling/sass-example/README.md +16 -0
- jac_client/examples/css-styling/sass-example/app.jac +63 -0
- jac_client/examples/css-styling/sass-example/package.json +29 -0
- jac_client/examples/css-styling/sass-example/styles.scss +158 -0
- jac_client/examples/css-styling/sass-example/vite.config.js +28 -0
- jac_client/examples/css-styling/styled-components/.babelrc +9 -0
- jac_client/examples/css-styling/styled-components/README.md +16 -0
- jac_client/examples/css-styling/styled-components/app.jac +66 -0
- jac_client/examples/css-styling/styled-components/package.json +29 -0
- jac_client/examples/css-styling/styled-components/styled.js +91 -0
- jac_client/examples/css-styling/styled-components/vite.config.js +28 -0
- jac_client/examples/css-styling/tailwind-example/.babelrc +9 -0
- jac_client/examples/css-styling/tailwind-example/README.md +16 -0
- jac_client/examples/css-styling/tailwind-example/app.jac +64 -0
- jac_client/examples/css-styling/tailwind-example/global.css +1 -0
- jac_client/examples/css-styling/tailwind-example/package.json +30 -0
- jac_client/examples/css-styling/tailwind-example/vite.config.js +30 -0
- jac_client/examples/full-stack-with-auth/.babelrc +9 -0
- jac_client/examples/full-stack-with-auth/README.md +16 -0
- jac_client/examples/full-stack-with-auth/app.jac +735 -0
- jac_client/examples/full-stack-with-auth/package.json +28 -0
- jac_client/examples/full-stack-with-auth/vite.config.js +30 -0
- jac_client/examples/with-router/.babelrc +9 -0
- jac_client/examples/with-router/README.md +17 -0
- jac_client/examples/with-router/app.jac +323 -0
- jac_client/examples/with-router/package.json +28 -0
- jac_client/examples/with-router/vite.config.js +28 -0
- jac_client/plugin/cli.py +95 -179
- jac_client/plugin/client.py +111 -2
- jac_client/plugin/client_runtime.jac +183 -890
- jac_client/plugin/vite_client_bundle.py +185 -205
- jac_client/tests/__init__.py +0 -1
- jac_client/tests/fixtures/{client_app.jac → basic-app/app.jac} +1 -1
- jac_client/tests/fixtures/cl_file/app.cl.jac +38 -0
- jac_client/tests/fixtures/cl_file/app.jac +15 -0
- jac_client/tests/fixtures/{client_app_with_antd.jac → client_app_with_antd/app.jac} +7 -0
- jac_client/tests/fixtures/{js_import.jac → js_import/app.jac} +2 -2
- jac_client/tests/fixtures/{relative_import.jac → relative_import/app.jac} +1 -1
- jac_client/tests/fixtures/{button.jac → relative_import/button.jac} +2 -2
- jac_client/tests/fixtures/spawn_test/app.jac +133 -0
- jac_client/tests/fixtures/{test_fragments_spread.jac → test_fragments_spread/app.jac} +11 -2
- jac_client/tests/test_asset_examples.py +339 -0
- jac_client/tests/test_cl.py +345 -151
- jac_client/tests/test_create_jac_app.py +41 -45
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/METADATA +72 -16
- jac_client-0.2.1.dist-info/RECORD +140 -0
- jac_client/examples/little-x/package-lock.json +0 -2840
- jac_client/examples/todo-app/README.md +0 -82
- jac_client/examples/todo-app/app.jac +0 -683
- jac_client/examples/todo-app/package-lock.json +0 -999
- jac_client/examples/todo-app/package.json +0 -22
- jac_client-0.1.0.dist-info/RECORD +0 -33
- /jac_client/tests/fixtures/{utils.js → js_import/utils.js} +0 -0
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/WHEEL +0 -0
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/entry_points.txt +0 -0
jac_client/docs/routing.md
CHANGED
|
@@ -1,530 +1,660 @@
|
|
|
1
1
|
# Routing in Jac: Building Multi-Page Applications
|
|
2
2
|
|
|
3
|
-
Learn how to
|
|
3
|
+
Learn how to create multi-page applications with client-side routing using Jac's declarative routing API.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## 📚 Table of Contents
|
|
8
8
|
|
|
9
9
|
- [What is Routing?](#what-is-routing)
|
|
10
|
+
- [Getting Started](#getting-started)
|
|
10
11
|
- [Basic Routing Setup](#basic-routing-setup)
|
|
11
|
-
- [Route
|
|
12
|
-
- [Navigation](#navigation)
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
12
|
+
- [Route Components](#route-components)
|
|
13
|
+
- [Navigation with Link](#navigation-with-link)
|
|
14
|
+
- [Programmatic Navigation with useNavigate](#programmatic-navigation-with-usenavigate)
|
|
15
|
+
- [URL Parameters with useParams](#url-parameters-with-useparams)
|
|
16
|
+
- [Current Location with useLocation](#current-location-with-uselocation)
|
|
17
|
+
- [Protected Routes Pattern](#protected-routes-pattern)
|
|
18
|
+
- [Complete Examples](#complete-examples)
|
|
19
|
+
- [Best Practices](#best-practices)
|
|
16
20
|
|
|
17
21
|
---
|
|
18
22
|
|
|
19
23
|
## What is Routing?
|
|
20
24
|
|
|
21
|
-
Routing allows you to create multi-page applications where different URLs display different components
|
|
25
|
+
Routing allows you to create multi-page applications where different URLs display different components without page refreshes.
|
|
22
26
|
|
|
23
27
|
**Key Benefits:**
|
|
24
28
|
- **Single Page Application (SPA)**: No page refreshes when navigating
|
|
25
|
-
- **
|
|
29
|
+
- **Declarative Syntax**: Define routes using JSX components
|
|
30
|
+
- **URL Parameters**: Dynamic routes with params like `/user/:id`
|
|
26
31
|
- **Browser History**: Back/forward buttons work automatically
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
32
|
+
- **Hash-based URLs**: Uses `#/path` for maximum compatibility
|
|
33
|
+
- **Battle-tested**: Built on industry-standard routing technology
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Getting Started
|
|
38
|
+
|
|
39
|
+
Import routing components from `@jac-client/utils`:
|
|
40
|
+
|
|
41
|
+
```jac
|
|
42
|
+
cl import from "@jac-client/utils" {
|
|
43
|
+
Router,
|
|
44
|
+
Routes,
|
|
45
|
+
Route,
|
|
46
|
+
Link,
|
|
47
|
+
Navigate,
|
|
48
|
+
useNavigate,
|
|
49
|
+
useLocation,
|
|
50
|
+
useParams
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Core Components:**
|
|
55
|
+
- **`<Router>`**: Container that wraps your entire application
|
|
56
|
+
- **`<Routes>`**: Groups multiple routes together
|
|
57
|
+
- **`<Route>`**: Defines a single route with path and element
|
|
58
|
+
- **`<Link>`**: Navigation links that don't refresh the page
|
|
59
|
+
- **`<Navigate>`**: Component for conditional redirects
|
|
60
|
+
|
|
61
|
+
**Hooks:**
|
|
62
|
+
- **`useNavigate()`**: Get navigate function for programmatic navigation
|
|
63
|
+
- **`useLocation()`**: Access current location and pathname
|
|
64
|
+
- **`useParams()`**: Access URL parameters from dynamic routes
|
|
29
65
|
|
|
30
66
|
---
|
|
31
67
|
|
|
32
68
|
## Basic Routing Setup
|
|
33
69
|
|
|
34
|
-
###
|
|
70
|
+
### Simple Three-Page App
|
|
35
71
|
|
|
36
72
|
```jac
|
|
73
|
+
cl import from react { useState, useEffect }
|
|
74
|
+
cl import from "@jac-client/utils" { Router, Routes, Route, Link }
|
|
75
|
+
|
|
37
76
|
cl {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
77
|
+
# Page Components
|
|
78
|
+
def Home() -> any {
|
|
79
|
+
return <div>
|
|
80
|
+
<h1>🏠 Home Page</h1>
|
|
81
|
+
<p>Welcome to the home page!</p>
|
|
82
|
+
</div>;
|
|
83
|
+
}
|
|
44
84
|
|
|
45
|
-
|
|
46
|
-
|
|
85
|
+
def About() -> any {
|
|
86
|
+
return <div>
|
|
87
|
+
<h1>ℹ️ About Page</h1>
|
|
88
|
+
<p>Learn more about our application.</p>
|
|
89
|
+
</div>;
|
|
90
|
+
}
|
|
47
91
|
|
|
48
|
-
|
|
92
|
+
def Contact() -> any {
|
|
49
93
|
return <div>
|
|
50
|
-
|
|
94
|
+
<h1>📧 Contact Page</h1>
|
|
95
|
+
<p>Email: contact@example.com</p>
|
|
51
96
|
</div>;
|
|
52
97
|
}
|
|
53
98
|
|
|
54
|
-
|
|
55
|
-
|
|
99
|
+
# Main App with React Router
|
|
100
|
+
def app() -> any {
|
|
101
|
+
return <Router>
|
|
102
|
+
<div>
|
|
103
|
+
<nav>
|
|
104
|
+
<Link to="/">Home</Link>
|
|
105
|
+
{" | "}
|
|
106
|
+
<Link to="/about">About</Link>
|
|
107
|
+
{" | "}
|
|
108
|
+
<Link to="/contact">Contact</Link>
|
|
109
|
+
</nav>
|
|
110
|
+
<Routes>
|
|
111
|
+
<Route path="/" element={<Home />} />
|
|
112
|
+
<Route path="/about" element={<About />} />
|
|
113
|
+
<Route path="/contact" element={<Contact />} />
|
|
114
|
+
</Routes>
|
|
115
|
+
</div>
|
|
116
|
+
</Router>;
|
|
56
117
|
}
|
|
57
118
|
}
|
|
58
119
|
```
|
|
59
120
|
|
|
60
|
-
**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
121
|
+
**How It Works:**
|
|
122
|
+
1. **`<Router>`** wraps your entire app and manages routing state
|
|
123
|
+
2. **`<Routes>`** contains all your route definitions
|
|
124
|
+
3. **`<Route>`** maps a URL path to an element (note: `element={<Component />}`)
|
|
125
|
+
4. **`<Link>`** creates clickable navigation links
|
|
126
|
+
5. URLs will be hash-based: `#/`, `#/about`, `#/contact`
|
|
127
|
+
|
|
128
|
+
**Key Points:**
|
|
129
|
+
- ✅ Use `element={<Home />}` to render components
|
|
130
|
+
- ✅ No configuration needed - just wrap and go
|
|
131
|
+
- ✅ Hash-based URLs work everywhere
|
|
64
132
|
|
|
65
133
|
---
|
|
66
134
|
|
|
67
|
-
## Route
|
|
135
|
+
## Route Components
|
|
68
136
|
|
|
69
|
-
|
|
137
|
+
### Router Component
|
|
70
138
|
|
|
71
|
-
|
|
139
|
+
The `<Router>` component is the top-level container for your app:
|
|
72
140
|
|
|
73
141
|
```jac
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return TodoApp();
|
|
78
|
-
},
|
|
79
|
-
"guard": jacIsLoggedIn # Optional: route guard function
|
|
80
|
-
};
|
|
142
|
+
<Router>
|
|
143
|
+
{/* Your app content */}
|
|
144
|
+
</Router>
|
|
81
145
|
```
|
|
82
146
|
|
|
83
|
-
|
|
147
|
+
**Features:**
|
|
148
|
+
- Hash-based URLs (e.g., `#/about`, `#/contact`)
|
|
149
|
+
- No props needed - it just works!
|
|
150
|
+
- Manages routing state automatically
|
|
151
|
+
- Works in any environment
|
|
84
152
|
|
|
85
|
-
|
|
86
|
-
|----------|------|----------|-------------|
|
|
87
|
-
| `path` | `str` | ✅ Yes | URL path (must start with `/`) |
|
|
88
|
-
| `component` | `function` | ✅ Yes | Function that returns a JSX component |
|
|
89
|
-
| `guard` | `function` | ❌ No | Function that returns `True` if route is accessible |
|
|
153
|
+
### Routes Component
|
|
90
154
|
|
|
91
|
-
|
|
155
|
+
The `<Routes>` component groups multiple routes:
|
|
92
156
|
|
|
93
157
|
```jac
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
"guard": None
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
login_route = {
|
|
104
|
-
"path": "/login",
|
|
105
|
-
"component": lambda -> any { return LoginForm(); },
|
|
106
|
-
"guard": None
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
todos_route = {
|
|
110
|
-
"path": "/todos",
|
|
111
|
-
"component": lambda -> any { return TodoApp(); },
|
|
112
|
-
"guard": jacIsLoggedIn # Requires authentication
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
profile_route = {
|
|
116
|
-
"path": "/profile",
|
|
117
|
-
"component": lambda -> any { return ProfileView(); },
|
|
118
|
-
"guard": jacIsLoggedIn # Requires authentication
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
# Combine all routes
|
|
122
|
-
routes = [home_route, login_route, todos_route, profile_route];
|
|
123
|
-
|
|
124
|
-
# Initialize router with default route
|
|
125
|
-
router = initRouter(routes, "/");
|
|
158
|
+
<Routes>
|
|
159
|
+
<Route path="/" element={<Home />} />
|
|
160
|
+
<Route path="/about" element={<About />} />
|
|
161
|
+
<Route path="/contact" element={<Contact />} />
|
|
162
|
+
</Routes>
|
|
163
|
+
```
|
|
126
164
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
165
|
+
### Route Component
|
|
166
|
+
|
|
167
|
+
Each `<Route>` defines a single route:
|
|
168
|
+
|
|
169
|
+
```jac
|
|
170
|
+
<Route path="/todos" element={<TodoList />} />
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Props:**
|
|
174
|
+
- **`path`**: The URL path (must start with `/`)
|
|
175
|
+
- **`element`**: The JSX element to render (note: call the component with `<>`)
|
|
176
|
+
- **`index`**: Boolean for index routes (optional)
|
|
177
|
+
|
|
178
|
+
**Important:** Use `element={<Component />}` not `component={Component}`
|
|
179
|
+
|
|
180
|
+
### Example: Index Routes
|
|
181
|
+
|
|
182
|
+
```jac
|
|
183
|
+
<Routes>
|
|
184
|
+
<Route index element={<Home />} /> {/* Matches parent route */}
|
|
185
|
+
<Route path="/about" element={<About />} />
|
|
186
|
+
</Routes>
|
|
133
187
|
```
|
|
134
188
|
|
|
135
189
|
---
|
|
136
190
|
|
|
137
|
-
## Navigation
|
|
191
|
+
## Navigation with Link
|
|
138
192
|
|
|
139
|
-
|
|
193
|
+
### The Link Component
|
|
140
194
|
|
|
141
|
-
|
|
195
|
+
The `<Link>` component creates clickable navigation links:
|
|
142
196
|
|
|
143
197
|
```jac
|
|
144
|
-
|
|
145
|
-
async def handleLogin(e: any) -> None {
|
|
146
|
-
e.preventDefault();
|
|
147
|
-
success = await jacLogin(username, password);
|
|
148
|
-
if success {
|
|
149
|
-
navigate("/todos"); # Global navigate function
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
198
|
+
<Link to="/about">About Us</Link>
|
|
153
199
|
```
|
|
154
200
|
|
|
155
|
-
|
|
201
|
+
**Props:**
|
|
202
|
+
- **`to`**: The destination path (e.g., `"/"`, `"/about"`)
|
|
203
|
+
- **`style`**: Optional CSS styles for the link
|
|
204
|
+
- **`className`**: Optional CSS class name
|
|
156
205
|
|
|
157
|
-
|
|
206
|
+
### Basic Navigation
|
|
158
207
|
|
|
159
208
|
```jac
|
|
209
|
+
cl import from "@jac-client/utils" { Router, Routes, Route, Link }
|
|
210
|
+
|
|
160
211
|
cl {
|
|
161
|
-
def
|
|
162
|
-
return <nav>
|
|
163
|
-
<Link
|
|
164
|
-
|
|
165
|
-
<Link
|
|
212
|
+
def Navigation() -> any {
|
|
213
|
+
return <nav style={{"padding": "1rem", "backgroundColor": "#f0f0f0"}}>
|
|
214
|
+
<Link to="/">Home</Link>
|
|
215
|
+
{" | "}
|
|
216
|
+
<Link to="/about">About</Link>
|
|
217
|
+
{" | "}
|
|
218
|
+
<Link to="/contact">Contact</Link>
|
|
166
219
|
</nav>;
|
|
167
220
|
}
|
|
168
221
|
}
|
|
169
222
|
```
|
|
170
223
|
|
|
171
|
-
|
|
172
|
-
- Automatic hash-based navigation
|
|
173
|
-
- Updates URL without page refresh
|
|
174
|
-
- Triggers route changes and component updates
|
|
175
|
-
|
|
176
|
-
### Complete Navigation Example
|
|
224
|
+
### Active Link Styling with useLocation
|
|
177
225
|
|
|
178
226
|
```jac
|
|
179
|
-
cl {
|
|
180
|
-
def Nav(currentPath: str) -> any {
|
|
181
|
-
return <nav style={{
|
|
182
|
-
"display": "flex",
|
|
183
|
-
"gap": "16px",
|
|
184
|
-
"padding": "12px"
|
|
185
|
-
}}>
|
|
186
|
-
<Link href="/" style={{
|
|
187
|
-
"color": ("#7C3AED" if currentPath == "/" else "#111827"),
|
|
188
|
-
"textDecoration": "none",
|
|
189
|
-
"fontWeight": ("700" if currentPath == "/" else "500")
|
|
190
|
-
}}>
|
|
191
|
-
Home
|
|
192
|
-
</Link>
|
|
193
|
-
<Link href="/todos" style={{
|
|
194
|
-
"color": ("#7C3AED" if currentPath == "/todos" else "#111827"),
|
|
195
|
-
"textDecoration": "none",
|
|
196
|
-
"fontWeight": ("700" if currentPath == "/todos" else "500")
|
|
197
|
-
}}>
|
|
198
|
-
Todos
|
|
199
|
-
</Link>
|
|
200
|
-
<button onClick={lambda -> None {
|
|
201
|
-
navigate("/login");
|
|
202
|
-
}}>
|
|
203
|
-
Logout
|
|
204
|
-
</button>
|
|
205
|
-
</nav>;
|
|
206
|
-
}
|
|
227
|
+
cl import from "@jac-client/utils" { Link, useLocation }
|
|
207
228
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
229
|
+
cl {
|
|
230
|
+
def Navigation() -> any {
|
|
231
|
+
let location = useLocation();
|
|
232
|
+
|
|
233
|
+
def linkStyle(path: str) -> dict {
|
|
234
|
+
isActive = location.pathname == path;
|
|
235
|
+
return {
|
|
236
|
+
"padding": "0.5rem 1rem",
|
|
237
|
+
"textDecoration": "none",
|
|
238
|
+
"color": "#0066cc" if isActive else "#333",
|
|
239
|
+
"fontWeight": "bold" if isActive else "normal",
|
|
240
|
+
"backgroundColor": "#e3f2fd" if isActive else "transparent",
|
|
241
|
+
"borderRadius": "4px"
|
|
242
|
+
};
|
|
243
|
+
}
|
|
212
244
|
|
|
213
|
-
return <
|
|
214
|
-
{
|
|
215
|
-
{
|
|
216
|
-
|
|
245
|
+
return <nav style={{"display": "flex", "gap": "1rem", "padding": "1rem"}}>
|
|
246
|
+
<Link to="/" style={linkStyle("/")}>Home</Link>
|
|
247
|
+
<Link to="/about" style={linkStyle("/about")}>About</Link>
|
|
248
|
+
<Link to="/contact" style={linkStyle("/contact")}>Contact</Link>
|
|
249
|
+
</nav>;
|
|
217
250
|
}
|
|
218
251
|
}
|
|
219
252
|
```
|
|
220
253
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
## Route Guards
|
|
254
|
+
### Link Component Features
|
|
224
255
|
|
|
225
|
-
|
|
256
|
+
- **No Page Refresh**: Navigation happens without reloading the page
|
|
257
|
+
- **Client-Side Routing**: Fast transitions between pages
|
|
258
|
+
- **Browser History**: Works with browser back/forward buttons
|
|
259
|
+
- **Styling Support**: Can be styled like any other element
|
|
260
|
+
- **Battle-tested**: Reliable, production-ready navigation
|
|
226
261
|
|
|
227
|
-
|
|
262
|
+
---
|
|
228
263
|
|
|
229
|
-
|
|
230
|
-
2. **Automatic Check**: Router checks the guard when the route matches
|
|
231
|
-
3. **Access Denied**: If guard returns `False`, shows "Access Denied" instead of the component
|
|
264
|
+
## Programmatic Navigation with useNavigate
|
|
232
265
|
|
|
233
|
-
|
|
266
|
+
For programmatic navigation (e.g., after form submission), use the `useNavigate()` hook:
|
|
234
267
|
|
|
235
268
|
```jac
|
|
269
|
+
cl import from "@jac-client/utils" { useNavigate }
|
|
270
|
+
|
|
236
271
|
cl {
|
|
237
|
-
def
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
"
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
272
|
+
def LoginForm() -> any {
|
|
273
|
+
let [username, setUsername] = useState("");
|
|
274
|
+
let [password, setPassword] = useState("");
|
|
275
|
+
let navigate = useNavigate();
|
|
276
|
+
|
|
277
|
+
async def handleLogin(e: any) -> None {
|
|
278
|
+
e.preventDefault();
|
|
279
|
+
success = await jacLogin(username, password);
|
|
280
|
+
if success {
|
|
281
|
+
navigate("/dashboard"); # Navigate after successful login
|
|
282
|
+
} else {
|
|
283
|
+
alert("Login failed");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return <form onSubmit={handleLogin}>
|
|
288
|
+
<input
|
|
289
|
+
type="text"
|
|
290
|
+
value={username}
|
|
291
|
+
onChange={lambda e: any -> None { setUsername(e.target.value); }}
|
|
292
|
+
placeholder="Username"
|
|
293
|
+
/>
|
|
294
|
+
<input
|
|
295
|
+
type="password"
|
|
296
|
+
value={password}
|
|
297
|
+
onChange={lambda e: any -> None { setPassword(e.target.value); }}
|
|
298
|
+
placeholder="Password"
|
|
299
|
+
/>
|
|
300
|
+
<button type="submit">Login</button>
|
|
301
|
+
</form>;
|
|
254
302
|
}
|
|
255
303
|
}
|
|
256
304
|
```
|
|
257
305
|
|
|
258
|
-
|
|
306
|
+
**useNavigate() Features:**
|
|
307
|
+
- **Hook-based API**: Modern React pattern
|
|
308
|
+
- **Type-safe**: Works seamlessly with TypeScript/Jac types
|
|
309
|
+
- **Replace option**: Use `navigate("/path", { replace: true })` to replace history entry
|
|
310
|
+
|
|
311
|
+
**Common Use Cases:**
|
|
312
|
+
- After form submission
|
|
313
|
+
- After authentication
|
|
314
|
+
- Conditional navigation based on logic
|
|
315
|
+
- In button onClick handlers
|
|
316
|
+
- Redirects after API calls
|
|
317
|
+
|
|
318
|
+
---
|
|
259
319
|
|
|
260
|
-
|
|
320
|
+
## URL Parameters with useParams
|
|
321
|
+
|
|
322
|
+
Access dynamic URL parameters using the `useParams()` hook:
|
|
261
323
|
|
|
262
324
|
```jac
|
|
325
|
+
cl import from "@jac-client/utils" { useParams, Link }
|
|
326
|
+
|
|
263
327
|
cl {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
328
|
+
def UserProfile() -> any {
|
|
329
|
+
let params = useParams();
|
|
330
|
+
let userId = params.id;
|
|
331
|
+
|
|
332
|
+
return <div>
|
|
333
|
+
<h1>User Profile</h1>
|
|
334
|
+
<p>Viewing profile for user ID: {userId}</p>
|
|
335
|
+
<Link to="/">Back to Home</Link>
|
|
336
|
+
</div>;
|
|
271
337
|
}
|
|
272
338
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
339
|
+
def app() -> any {
|
|
340
|
+
return <Router>
|
|
341
|
+
<Routes>
|
|
342
|
+
<Route path="/" element={<Home />} />
|
|
343
|
+
<Route path="/user/:id" element={<UserProfile />} />
|
|
344
|
+
</Routes>
|
|
345
|
+
</Router>;
|
|
280
346
|
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**URL Pattern Examples:**
|
|
351
|
+
- `/user/:id` → Access via `params.id`
|
|
352
|
+
- `/posts/:postId/comments/:commentId` → Access via `params.postId` and `params.commentId`
|
|
353
|
+
- `/products/:category/:productId` → Multiple parameters
|
|
354
|
+
|
|
355
|
+
---
|
|
281
356
|
|
|
282
|
-
|
|
283
|
-
admin_route = {
|
|
284
|
-
"path": "/admin",
|
|
285
|
-
"component": lambda -> any { return AdminView(); },
|
|
286
|
-
"guard": isAdmin # Custom guard
|
|
287
|
-
};
|
|
357
|
+
## Current Location with useLocation
|
|
288
358
|
|
|
289
|
-
|
|
290
|
-
"path": "/settings",
|
|
291
|
-
"component": lambda -> any { return SettingsView(); },
|
|
292
|
-
"guard": lambda -> bool { return hasPermission("settings:edit"); } # Inline guard
|
|
293
|
-
};
|
|
359
|
+
Access the current location object using `useLocation()`:
|
|
294
360
|
|
|
295
|
-
|
|
296
|
-
|
|
361
|
+
```jac
|
|
362
|
+
cl import from "@jac-client/utils" { useLocation }
|
|
363
|
+
|
|
364
|
+
cl {
|
|
365
|
+
def CurrentPath() -> any {
|
|
366
|
+
let location = useLocation();
|
|
297
367
|
|
|
298
|
-
return <div>
|
|
368
|
+
return <div>
|
|
369
|
+
<p>Current pathname: {location.pathname}</p>
|
|
370
|
+
<p>Current hash: {location.hash}</p>
|
|
371
|
+
<p>Search params: {location.search}</p>
|
|
372
|
+
</div>;
|
|
299
373
|
}
|
|
300
374
|
}
|
|
301
375
|
```
|
|
302
376
|
|
|
303
|
-
|
|
377
|
+
**Location Object Properties:**
|
|
378
|
+
- **`pathname`**: Current path (e.g., `/about`)
|
|
379
|
+
- **`search`**: Query string (e.g., `?page=2`)
|
|
380
|
+
- **`hash`**: URL hash (e.g., `#section1`)
|
|
381
|
+
- **`state`**: Location state passed via navigate
|
|
304
382
|
|
|
305
|
-
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Protected Routes Pattern
|
|
386
|
+
|
|
387
|
+
Use the `<Navigate>` component to protect routes that require authentication:
|
|
306
388
|
|
|
307
389
|
```jac
|
|
390
|
+
cl import from "@jac-client/utils" { Navigate, useNavigate }
|
|
391
|
+
|
|
308
392
|
cl {
|
|
309
|
-
def
|
|
393
|
+
def Dashboard() -> any {
|
|
394
|
+
# Check if user is logged in
|
|
310
395
|
if not jacIsLoggedIn() {
|
|
311
|
-
|
|
312
|
-
return False;
|
|
396
|
+
return <Navigate to="/login" />;
|
|
313
397
|
}
|
|
314
|
-
|
|
398
|
+
|
|
399
|
+
return <div>
|
|
400
|
+
<h1>🎉 Dashboard</h1>
|
|
401
|
+
<p>Welcome! You are logged in.</p>
|
|
402
|
+
<p>This is protected content only visible to authenticated users.</p>
|
|
403
|
+
</div>;
|
|
315
404
|
}
|
|
316
405
|
|
|
317
|
-
def
|
|
318
|
-
|
|
319
|
-
"path": "/todos",
|
|
320
|
-
"component": lambda -> any { return TodoApp(); },
|
|
321
|
-
"guard": protectedGuard
|
|
322
|
-
};
|
|
406
|
+
def LoginPage() -> any {
|
|
407
|
+
let navigate = useNavigate();
|
|
323
408
|
|
|
324
|
-
|
|
325
|
-
|
|
409
|
+
async def handleLogin(e: any) -> None {
|
|
410
|
+
e.preventDefault();
|
|
411
|
+
success = await jacLogin(username, password);
|
|
412
|
+
if success {
|
|
413
|
+
navigate("/dashboard");
|
|
414
|
+
}
|
|
415
|
+
}
|
|
326
416
|
|
|
327
|
-
return <
|
|
417
|
+
return <form onSubmit={handleLogin}>
|
|
418
|
+
<h2>Login</h2>
|
|
419
|
+
<input type="text" placeholder="Username" />
|
|
420
|
+
<input type="password" placeholder="Password" />
|
|
421
|
+
<button type="submit">Login</button>
|
|
422
|
+
</form>;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
def app() -> any {
|
|
426
|
+
return <Router>
|
|
427
|
+
<Routes>
|
|
428
|
+
<Route path="/" element={<HomePage />} />
|
|
429
|
+
<Route path="/login" element={<LoginPage />} />
|
|
430
|
+
<Route path="/dashboard" element={<Dashboard />} />
|
|
431
|
+
</Routes>
|
|
432
|
+
</Router>;
|
|
328
433
|
}
|
|
329
434
|
}
|
|
330
435
|
```
|
|
331
436
|
|
|
437
|
+
**Protected Route Pattern:**
|
|
438
|
+
1. Check authentication at the start of the component
|
|
439
|
+
2. Return `<Navigate to="/login" />` if not authenticated
|
|
440
|
+
3. Return protected content if authenticated
|
|
441
|
+
4. Use `useNavigate()` to redirect after successful login
|
|
442
|
+
|
|
332
443
|
---
|
|
333
444
|
|
|
334
|
-
## Complete
|
|
445
|
+
## Complete Examples
|
|
335
446
|
|
|
336
|
-
|
|
447
|
+
### Example 1: Simple Multi-Page App
|
|
337
448
|
|
|
338
449
|
```jac
|
|
450
|
+
cl import from react { useState, useEffect }
|
|
451
|
+
cl import from "@jac-client/utils" { Router, Routes, Route, Link, useLocation }
|
|
452
|
+
|
|
339
453
|
cl {
|
|
340
|
-
def
|
|
341
|
-
|
|
342
|
-
|
|
454
|
+
def Navigation() -> any {
|
|
455
|
+
let location = useLocation();
|
|
456
|
+
|
|
457
|
+
def linkStyle(path: str) -> dict {
|
|
458
|
+
isActive = location.pathname == path;
|
|
459
|
+
return {
|
|
460
|
+
"padding": "0.5rem 1rem",
|
|
461
|
+
"textDecoration": "none",
|
|
462
|
+
"color": "#0066cc" if isActive else "#333",
|
|
463
|
+
"fontWeight": "bold" if isActive else "normal"
|
|
464
|
+
};
|
|
343
465
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
"
|
|
347
|
-
"
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
"margin": "0 auto",
|
|
352
|
-
"display": "flex",
|
|
353
|
-
"gap": "16px",
|
|
354
|
-
"alignItems": "center"
|
|
355
|
-
}}>
|
|
356
|
-
<Link href="/todos" style={{"textDecoration": "none"}}>
|
|
357
|
-
<span style={{
|
|
358
|
-
"color": "#111827",
|
|
359
|
-
"fontWeight": "800",
|
|
360
|
-
"fontSize": "18px"
|
|
361
|
-
}}>📝 My Todos</span>
|
|
362
|
-
</Link>
|
|
363
|
-
<button
|
|
364
|
-
onClick={logout_action}
|
|
365
|
-
style={{
|
|
366
|
-
"marginLeft": "auto",
|
|
367
|
-
"padding": "8px 12px",
|
|
368
|
-
"background": "#FFFFFF",
|
|
369
|
-
"color": "#374151",
|
|
370
|
-
"border": "1px solid #E5E7EB",
|
|
371
|
-
"borderRadius": "18px",
|
|
372
|
-
"cursor": "pointer"
|
|
373
|
-
}}
|
|
374
|
-
>
|
|
375
|
-
Logout
|
|
376
|
-
</button>
|
|
377
|
-
</div>
|
|
466
|
+
|
|
467
|
+
return <nav style={{"padding": "1rem", "backgroundColor": "#f0f0f0"}}>
|
|
468
|
+
<Link to="/" style={linkStyle("/")}>Home</Link>
|
|
469
|
+
{" | "}
|
|
470
|
+
<Link to="/about" style={linkStyle("/about")}>About</Link>
|
|
471
|
+
{" | "}
|
|
472
|
+
<Link to="/contact" style={linkStyle("/contact")}>Contact</Link>
|
|
378
473
|
</nav>;
|
|
379
474
|
}
|
|
380
475
|
|
|
381
|
-
def
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
"component": lambda -> any { return LoginForm(); },
|
|
386
|
-
"guard": None
|
|
387
|
-
};
|
|
388
|
-
|
|
389
|
-
signup_route = {
|
|
390
|
-
"path": "/signup",
|
|
391
|
-
"component": lambda -> any { return SignupForm(); },
|
|
392
|
-
"guard": None
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
todos_route = {
|
|
396
|
-
"path": "/todos",
|
|
397
|
-
"component": lambda -> any { return TodoApp(); },
|
|
398
|
-
"guard": jacIsLoggedIn # Protected route
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
# Initialize router
|
|
402
|
-
routes = [login_route, signup_route, todos_route];
|
|
403
|
-
router = initRouter(routes, "/login"); # Default to login page
|
|
404
|
-
|
|
405
|
-
# Get current path for navigation
|
|
406
|
-
currentPath = router.path();
|
|
407
|
-
|
|
408
|
-
return <div style={{
|
|
409
|
-
"minHeight": "95vh",
|
|
410
|
-
"background": "#F7F8FA",
|
|
411
|
-
"padding": "24px"
|
|
412
|
-
}}>
|
|
413
|
-
{Nav(currentPath)}
|
|
414
|
-
<div style={{
|
|
415
|
-
"maxWidth": "960px",
|
|
416
|
-
"margin": "0 auto",
|
|
417
|
-
"padding": "20px"
|
|
418
|
-
}}>
|
|
419
|
-
{router.render()}
|
|
420
|
-
</div>
|
|
476
|
+
def Home() -> any {
|
|
477
|
+
return <div>
|
|
478
|
+
<h1>🏠 Home Page</h1>
|
|
479
|
+
<p>Welcome to the home page!</p>
|
|
421
480
|
</div>;
|
|
422
481
|
}
|
|
423
482
|
|
|
424
|
-
def
|
|
425
|
-
return
|
|
483
|
+
def About() -> any {
|
|
484
|
+
return <div>
|
|
485
|
+
<h1>ℹ️ About Page</h1>
|
|
486
|
+
<p>Learn more about our application.</p>
|
|
487
|
+
</div>;
|
|
426
488
|
}
|
|
427
|
-
}
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
---
|
|
431
489
|
|
|
432
|
-
|
|
490
|
+
def Contact() -> any {
|
|
491
|
+
return <div>
|
|
492
|
+
<h1>📧 Contact Page</h1>
|
|
493
|
+
<p>Email: contact@example.com</p>
|
|
494
|
+
</div>;
|
|
495
|
+
}
|
|
433
496
|
|
|
434
|
-
|
|
497
|
+
def app() -> any {
|
|
498
|
+
return <Router>
|
|
499
|
+
<div>
|
|
500
|
+
<Navigation />
|
|
501
|
+
<div style={{"padding": "2rem"}}>
|
|
502
|
+
<Routes>
|
|
503
|
+
<Route path="/" element={<Home />} />
|
|
504
|
+
<Route path="/about" element={<About />} />
|
|
505
|
+
<Route path="/contact" element={<Contact />} />
|
|
506
|
+
</Routes>
|
|
507
|
+
</div>
|
|
508
|
+
</div>
|
|
509
|
+
</Router>;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
```
|
|
435
513
|
|
|
436
|
-
|
|
514
|
+
### Example 2: App with URL Parameters
|
|
437
515
|
|
|
438
516
|
```jac
|
|
517
|
+
cl import from "@jac-client/utils" { Router, Routes, Route, Link, useParams }
|
|
518
|
+
|
|
439
519
|
cl {
|
|
440
|
-
def
|
|
441
|
-
|
|
442
|
-
|
|
520
|
+
def UserList() -> any {
|
|
521
|
+
users = ["Alice", "Bob", "Charlie"];
|
|
522
|
+
|
|
523
|
+
return <div>
|
|
524
|
+
<h1>👥 User List</h1>
|
|
525
|
+
{users.map(lambda user: any -> any {
|
|
526
|
+
return <div key={user}>
|
|
527
|
+
<Link to={"/user/" + user}>{user}</Link>
|
|
528
|
+
</div>;
|
|
529
|
+
})}
|
|
530
|
+
</div>;
|
|
531
|
+
}
|
|
443
532
|
|
|
444
|
-
|
|
533
|
+
def UserProfile() -> any {
|
|
534
|
+
let params = useParams();
|
|
535
|
+
let username = params.id;
|
|
445
536
|
|
|
446
|
-
# Use currentPath for conditional rendering
|
|
447
537
|
return <div>
|
|
448
|
-
{
|
|
449
|
-
{
|
|
538
|
+
<h1>👤 Profile: {username}</h1>
|
|
539
|
+
<p>Viewing profile for {username}</p>
|
|
540
|
+
<Link to="/">← Back to User List</Link>
|
|
450
541
|
</div>;
|
|
451
542
|
}
|
|
543
|
+
|
|
544
|
+
def app() -> any {
|
|
545
|
+
return <Router>
|
|
546
|
+
<Routes>
|
|
547
|
+
<Route path="/" element={<UserList />} />
|
|
548
|
+
<Route path="/user/:id" element={<UserProfile />} />
|
|
549
|
+
</Routes>
|
|
550
|
+
</Router>;
|
|
551
|
+
}
|
|
452
552
|
}
|
|
453
553
|
```
|
|
454
554
|
|
|
455
|
-
|
|
555
|
+
---
|
|
456
556
|
|
|
457
|
-
|
|
557
|
+
## Best Practices
|
|
458
558
|
|
|
559
|
+
### 1. **Use Correct Route Syntax**
|
|
459
560
|
```jac
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
e.preventDefault();
|
|
463
|
-
success = await jacLogin(username, password);
|
|
464
|
-
if success {
|
|
465
|
-
router = __jacReactiveContext.router;
|
|
466
|
-
if router {
|
|
467
|
-
router.navigate("/todos");
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
561
|
+
# ✅ CORRECT - Use element prop with JSX
|
|
562
|
+
<Route path="/" element={<Home />} />
|
|
471
563
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
564
|
+
# ❌ WRONG - Don't pass component without JSX
|
|
565
|
+
<Route path="/" component={Home} />
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### 2. **Import All Needed Components**
|
|
569
|
+
```jac
|
|
570
|
+
cl import from "@jac-client/utils" {
|
|
571
|
+
Router,
|
|
572
|
+
Routes,
|
|
573
|
+
Route,
|
|
574
|
+
Link,
|
|
575
|
+
Navigate,
|
|
576
|
+
useNavigate,
|
|
577
|
+
useLocation,
|
|
578
|
+
useParams
|
|
476
579
|
}
|
|
477
580
|
```
|
|
478
581
|
|
|
479
|
-
###
|
|
582
|
+
### 3. **Use Hooks for Navigation**
|
|
583
|
+
```jac
|
|
584
|
+
# ✅ CORRECT - Use useNavigate hook
|
|
585
|
+
def MyComponent() -> any {
|
|
586
|
+
let navigate = useNavigate();
|
|
587
|
+
navigate("/dashboard");
|
|
588
|
+
}
|
|
480
589
|
|
|
481
|
-
|
|
590
|
+
# ⚠️ OLD - Global navigate() function (still works for backward compatibility)
|
|
591
|
+
navigate("/dashboard");
|
|
592
|
+
```
|
|
482
593
|
|
|
594
|
+
### 4. **Protected Routes Pattern**
|
|
483
595
|
```jac
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
navigate("/todo-detail");
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
def TodoDetailView() -> any {
|
|
492
|
-
id = todoId();
|
|
493
|
-
todo = getTodoById(id); # Fetch todo by ID
|
|
494
|
-
return <div>{todo.text}</div>;
|
|
596
|
+
# ✅ CORRECT - Check auth in component
|
|
597
|
+
def ProtectedPage() -> any {
|
|
598
|
+
if not jacIsLoggedIn() {
|
|
599
|
+
return <Navigate to="/login" />;
|
|
495
600
|
}
|
|
601
|
+
return <div>Protected content</div>;
|
|
496
602
|
}
|
|
497
603
|
```
|
|
498
604
|
|
|
499
|
-
###
|
|
605
|
+
### 5. **Use Link for Navigation**
|
|
606
|
+
```jac
|
|
607
|
+
# ✅ CORRECT - Use Link component
|
|
608
|
+
<Link to="/about">About</Link>
|
|
500
609
|
|
|
501
|
-
|
|
610
|
+
# ❌ WRONG - Regular anchor tags cause page reload
|
|
611
|
+
<a href="#/about">About</a>
|
|
612
|
+
```
|
|
502
613
|
|
|
614
|
+
### 6. **Dynamic Routes with Parameters**
|
|
503
615
|
```jac
|
|
504
|
-
#
|
|
505
|
-
|
|
616
|
+
# Define route with parameter
|
|
617
|
+
<Route path="/user/:id" element={<UserProfile />} />
|
|
618
|
+
|
|
619
|
+
# Access parameter in component
|
|
620
|
+
def UserProfile() -> any {
|
|
621
|
+
let params = useParams();
|
|
622
|
+
let userId = params.id;
|
|
623
|
+
return <div>User: {userId}</div>;
|
|
624
|
+
}
|
|
506
625
|
```
|
|
507
626
|
|
|
508
|
-
|
|
627
|
+
### 7. **Active Link Styling**
|
|
628
|
+
```jac
|
|
629
|
+
def Navigation() -> any {
|
|
630
|
+
let location = useLocation();
|
|
509
631
|
|
|
510
|
-
|
|
632
|
+
def isActive(path: str) -> bool {
|
|
633
|
+
return location.pathname == path;
|
|
634
|
+
}
|
|
511
635
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
636
|
+
return <nav>
|
|
637
|
+
<Link
|
|
638
|
+
to="/"
|
|
639
|
+
style={{"fontWeight": "bold" if isActive("/") else "normal"}}
|
|
640
|
+
>
|
|
641
|
+
Home
|
|
642
|
+
</Link>
|
|
643
|
+
</nav>;
|
|
644
|
+
}
|
|
645
|
+
```
|
|
518
646
|
|
|
519
647
|
---
|
|
520
648
|
|
|
521
649
|
## Summary
|
|
522
650
|
|
|
523
|
-
- **
|
|
524
|
-
- **
|
|
525
|
-
- **
|
|
526
|
-
- **
|
|
527
|
-
- **
|
|
651
|
+
- **Simple & Declarative**: Use `<Router>`, `<Routes>`, `<Route>` components
|
|
652
|
+
- **Hash-based URLs**: Uses `#/path` for maximum compatibility
|
|
653
|
+
- **Modern Hooks**: `useNavigate()`, `useLocation()`, `useParams()`
|
|
654
|
+
- **Protected Routes**: Use `<Navigate>` component for redirects
|
|
655
|
+
- **URL Parameters**: Dynamic routes with `:param` syntax
|
|
656
|
+
- **No Configuration**: Just wrap your app in `<Router>` and start routing!
|
|
657
|
+
- **Production-ready**: Battle-tested routing for real applications
|
|
528
658
|
|
|
529
|
-
Routing in Jac is simple,
|
|
659
|
+
Routing in Jac is simple, powerful, and production-ready! 🚀
|
|
530
660
|
|