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.
Files changed (141) hide show
  1. jac_client/docs/README.md +232 -172
  2. jac_client/docs/advanced-state.md +1012 -452
  3. jac_client/docs/asset-serving/intro.md +209 -0
  4. jac_client/docs/assets/pipe_line-v2.svg +32 -0
  5. jac_client/docs/assets/pipe_line.png +0 -0
  6. jac_client/docs/file-system/intro.md +90 -0
  7. jac_client/docs/guide-example/intro.md +117 -0
  8. jac_client/docs/guide-example/step-01-setup.md +260 -0
  9. jac_client/docs/guide-example/step-02-components.md +416 -0
  10. jac_client/docs/guide-example/step-03-styling.md +478 -0
  11. jac_client/docs/guide-example/step-04-todo-ui.md +477 -0
  12. jac_client/docs/guide-example/step-05-local-state.md +530 -0
  13. jac_client/docs/guide-example/step-06-events.md +750 -0
  14. jac_client/docs/guide-example/step-07-effects.md +469 -0
  15. jac_client/docs/guide-example/step-08-walkers.md +534 -0
  16. jac_client/docs/guide-example/step-09-authentication.md +586 -0
  17. jac_client/docs/guide-example/step-10-routing.md +540 -0
  18. jac_client/docs/guide-example/step-11-final.md +964 -0
  19. jac_client/docs/imports.md +538 -46
  20. jac_client/docs/lifecycle-hooks.md +517 -297
  21. jac_client/docs/routing.md +487 -357
  22. jac_client/docs/styling/intro.md +250 -0
  23. jac_client/docs/styling/js-styling.md +373 -0
  24. jac_client/docs/styling/material-ui.md +346 -0
  25. jac_client/docs/styling/pure-css.md +305 -0
  26. jac_client/docs/styling/sass.md +409 -0
  27. jac_client/docs/styling/styled-components.md +401 -0
  28. jac_client/docs/styling/tailwind.md +303 -0
  29. jac_client/examples/asset-serving/css-with-image/.babelrc +9 -0
  30. jac_client/examples/asset-serving/css-with-image/README.md +91 -0
  31. jac_client/examples/asset-serving/css-with-image/app.jac +67 -0
  32. jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
  33. jac_client/examples/asset-serving/css-with-image/package.json +28 -0
  34. jac_client/examples/asset-serving/css-with-image/styles.css +27 -0
  35. jac_client/examples/asset-serving/css-with-image/vite.config.js +29 -0
  36. jac_client/examples/asset-serving/image-asset/.babelrc +9 -0
  37. jac_client/examples/asset-serving/image-asset/README.md +119 -0
  38. jac_client/examples/asset-serving/image-asset/app.jac +43 -0
  39. jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
  40. jac_client/examples/asset-serving/image-asset/package.json +28 -0
  41. jac_client/examples/asset-serving/image-asset/styles.css +27 -0
  42. jac_client/examples/asset-serving/image-asset/vite.config.js +29 -0
  43. jac_client/examples/asset-serving/import-alias/.babelrc +9 -0
  44. jac_client/examples/asset-serving/import-alias/README.md +83 -0
  45. jac_client/examples/asset-serving/import-alias/app.jac +57 -0
  46. jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
  47. jac_client/examples/asset-serving/import-alias/package.json +28 -0
  48. jac_client/examples/asset-serving/import-alias/vite.config.js +29 -0
  49. jac_client/examples/basic/.babelrc +9 -0
  50. jac_client/examples/basic/README.md +16 -0
  51. jac_client/examples/basic/app.jac +16 -0
  52. jac_client/examples/basic/package.json +27 -0
  53. jac_client/examples/basic/vite.config.js +28 -0
  54. jac_client/examples/basic-auth/.babelrc +9 -0
  55. jac_client/examples/basic-auth/README.md +16 -0
  56. jac_client/examples/basic-auth/app.jac +308 -0
  57. jac_client/examples/basic-auth/package.json +27 -0
  58. jac_client/examples/basic-auth/vite.config.js +28 -0
  59. jac_client/examples/basic-auth-with-router/.babelrc +9 -0
  60. jac_client/examples/basic-auth-with-router/README.md +60 -0
  61. jac_client/examples/basic-auth-with-router/app.jac +464 -0
  62. jac_client/examples/basic-auth-with-router/package.json +28 -0
  63. jac_client/examples/basic-auth-with-router/vite.config.js +28 -0
  64. jac_client/examples/basic-full-stack/.babelrc +9 -0
  65. jac_client/examples/basic-full-stack/README.md +18 -0
  66. jac_client/examples/basic-full-stack/app.jac +320 -0
  67. jac_client/examples/basic-full-stack/package.json +28 -0
  68. jac_client/examples/basic-full-stack/vite.config.js +28 -0
  69. jac_client/examples/css-styling/js-styling/.babelrc +9 -0
  70. jac_client/examples/css-styling/js-styling/README.md +183 -0
  71. jac_client/examples/css-styling/js-styling/app.jac +63 -0
  72. jac_client/examples/css-styling/js-styling/package.json +28 -0
  73. jac_client/examples/css-styling/js-styling/styles.js +100 -0
  74. jac_client/examples/css-styling/js-styling/vite.config.js +28 -0
  75. jac_client/examples/css-styling/material-ui/.babelrc +9 -0
  76. jac_client/examples/css-styling/material-ui/README.md +16 -0
  77. jac_client/examples/css-styling/material-ui/app.jac +82 -0
  78. jac_client/examples/css-styling/material-ui/package.json +32 -0
  79. jac_client/examples/css-styling/material-ui/vite.config.js +28 -0
  80. jac_client/examples/css-styling/pure-css/.babelrc +9 -0
  81. jac_client/examples/css-styling/pure-css/README.md +16 -0
  82. jac_client/examples/css-styling/pure-css/app.jac +63 -0
  83. jac_client/examples/css-styling/pure-css/package.json +28 -0
  84. jac_client/examples/css-styling/pure-css/styles.css +112 -0
  85. jac_client/examples/css-styling/pure-css/vite.config.js +28 -0
  86. jac_client/examples/css-styling/sass-example/.babelrc +9 -0
  87. jac_client/examples/css-styling/sass-example/README.md +16 -0
  88. jac_client/examples/css-styling/sass-example/app.jac +63 -0
  89. jac_client/examples/css-styling/sass-example/package.json +29 -0
  90. jac_client/examples/css-styling/sass-example/styles.scss +158 -0
  91. jac_client/examples/css-styling/sass-example/vite.config.js +28 -0
  92. jac_client/examples/css-styling/styled-components/.babelrc +9 -0
  93. jac_client/examples/css-styling/styled-components/README.md +16 -0
  94. jac_client/examples/css-styling/styled-components/app.jac +66 -0
  95. jac_client/examples/css-styling/styled-components/package.json +29 -0
  96. jac_client/examples/css-styling/styled-components/styled.js +91 -0
  97. jac_client/examples/css-styling/styled-components/vite.config.js +28 -0
  98. jac_client/examples/css-styling/tailwind-example/.babelrc +9 -0
  99. jac_client/examples/css-styling/tailwind-example/README.md +16 -0
  100. jac_client/examples/css-styling/tailwind-example/app.jac +64 -0
  101. jac_client/examples/css-styling/tailwind-example/global.css +1 -0
  102. jac_client/examples/css-styling/tailwind-example/package.json +30 -0
  103. jac_client/examples/css-styling/tailwind-example/vite.config.js +30 -0
  104. jac_client/examples/full-stack-with-auth/.babelrc +9 -0
  105. jac_client/examples/full-stack-with-auth/README.md +16 -0
  106. jac_client/examples/full-stack-with-auth/app.jac +735 -0
  107. jac_client/examples/full-stack-with-auth/package.json +28 -0
  108. jac_client/examples/full-stack-with-auth/vite.config.js +30 -0
  109. jac_client/examples/with-router/.babelrc +9 -0
  110. jac_client/examples/with-router/README.md +17 -0
  111. jac_client/examples/with-router/app.jac +323 -0
  112. jac_client/examples/with-router/package.json +28 -0
  113. jac_client/examples/with-router/vite.config.js +28 -0
  114. jac_client/plugin/cli.py +95 -179
  115. jac_client/plugin/client.py +111 -2
  116. jac_client/plugin/client_runtime.jac +183 -890
  117. jac_client/plugin/vite_client_bundle.py +185 -205
  118. jac_client/tests/__init__.py +0 -1
  119. jac_client/tests/fixtures/{client_app.jac → basic-app/app.jac} +1 -1
  120. jac_client/tests/fixtures/cl_file/app.cl.jac +38 -0
  121. jac_client/tests/fixtures/cl_file/app.jac +15 -0
  122. jac_client/tests/fixtures/{client_app_with_antd.jac → client_app_with_antd/app.jac} +7 -0
  123. jac_client/tests/fixtures/{js_import.jac → js_import/app.jac} +2 -2
  124. jac_client/tests/fixtures/{relative_import.jac → relative_import/app.jac} +1 -1
  125. jac_client/tests/fixtures/{button.jac → relative_import/button.jac} +2 -2
  126. jac_client/tests/fixtures/spawn_test/app.jac +133 -0
  127. jac_client/tests/fixtures/{test_fragments_spread.jac → test_fragments_spread/app.jac} +11 -2
  128. jac_client/tests/test_asset_examples.py +339 -0
  129. jac_client/tests/test_cl.py +345 -151
  130. jac_client/tests/test_create_jac_app.py +41 -45
  131. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/METADATA +72 -16
  132. jac_client-0.2.1.dist-info/RECORD +140 -0
  133. jac_client/examples/little-x/package-lock.json +0 -2840
  134. jac_client/examples/todo-app/README.md +0 -82
  135. jac_client/examples/todo-app/app.jac +0 -683
  136. jac_client/examples/todo-app/package-lock.json +0 -999
  137. jac_client/examples/todo-app/package.json +0 -22
  138. jac_client-0.1.0.dist-info/RECORD +0 -33
  139. /jac_client/tests/fixtures/{utils.js → js_import/utils.js} +0 -0
  140. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/WHEEL +0 -0
  141. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/entry_points.txt +0 -0
@@ -1,530 +1,660 @@
1
1
  # Routing in Jac: Building Multi-Page Applications
2
2
 
3
- Learn how to use `initRouter()` to create multi-page applications with navigation, route guards, and dynamic routing.
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 Configuration](#route-configuration)
12
- - [Navigation](#navigation)
13
- - [Route Guards](#route-guards)
14
- - [Complete Example](#complete-example)
15
- - [Advanced Patterns](#advanced-patterns)
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. In Jac, routing is handled by the `initRouter()` function, which manages navigation using hash-based routing (e.g., `#/login`, `#/todos`).
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
- - **URL-based Navigation**: Each view has its own URL
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
- - **Route Guards**: Protect routes with authentication checks
28
- - **Reactive Updates**: Route changes automatically update components
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
- ### Setting Up the Router
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
- def App() -> any {
39
- # Define routes
40
- routes = [
41
- {"path": "/", "component": lambda -> any { return HomeView(); }, "guard": None},
42
- {"path": "/about", "component": lambda -> any { return AboutView(); }, "guard": None}
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
- # Initialize router with default route
46
- router = initRouter(routes, "/");
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
- # Render the router
92
+ def Contact() -> any {
49
93
  return <div>
50
- {router.render()}
94
+ <h1>📧 Contact Page</h1>
95
+ <p>Email: contact@example.com</p>
51
96
  </div>;
52
97
  }
53
98
 
54
- def jac_app() -> any {
55
- return App();
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
- **Key Components:**
61
- - **`routes`**: Array of route configurations
62
- - **`initRouter()`**: Creates the router instance
63
- - **`router.render()`**: Renders the component for the current route
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 Configuration
135
+ ## Route Components
68
136
 
69
- Each route is a dictionary with three properties:
137
+ ### Router Component
70
138
 
71
- ### Route Structure
139
+ The `<Router>` component is the top-level container for your app:
72
140
 
73
141
  ```jac
74
- route = {
75
- "path": "/todos", # URL path
76
- "component": lambda -> any { # Component to render
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
- ### Route Properties
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
- | Property | Type | Required | Description |
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
- ### Example: Multiple Routes
155
+ The `<Routes>` component groups multiple routes:
92
156
 
93
157
  ```jac
94
- cl {
95
- def App() -> any {
96
- # Define all routes
97
- home_route = {
98
- "path": "/",
99
- "component": lambda -> any { return HomeView(); },
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
- return <div>
128
- {Nav(router.path())}
129
- {router.render()}
130
- </div>;
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
- Jac provides multiple ways to navigate between routes.
193
+ ### The Link Component
140
194
 
141
- ### Using the `navigate()` Runtime Function
195
+ The `<Link>` component creates clickable navigation links:
142
196
 
143
197
  ```jac
144
- cl {
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
- ### Using the `Link` Component
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
- The `Link` component creates clickable links that navigate to routes:
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 Nav() -> any {
162
- return <nav>
163
- <Link href="/">Home</Link>
164
- <Link href="/todos">Todos</Link>
165
- <Link href="/profile">Profile</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
- **Link Component Features:**
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
- def App() -> any {
209
- routes = [/* routes */];
210
- router = initRouter(routes, "/");
211
- currentPath = router.path();
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 <div>
214
- {Nav(currentPath)}
215
- {router.render()}
216
- </div>;
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
- Route guards protect routes by checking conditions (like authentication) before allowing access.
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
- ### How Guards Work
262
+ ---
228
263
 
229
- 1. **Guard Function**: A function that returns `True` (allow) or `False` (deny)
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
- ### Basic Guard Example
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 App() -> any {
238
- public_route = {
239
- "path": "/public",
240
- "component": lambda -> any { return PublicView(); },
241
- "guard": None # No guard - always accessible
242
- };
243
-
244
- protected_route = {
245
- "path": "/private",
246
- "component": lambda -> any { return PrivateView(); },
247
- "guard": jacIsLoggedIn # Requires authentication
248
- };
249
-
250
- routes = [public_route, protected_route];
251
- router = initRouter(routes, "/");
252
-
253
- return <div>{router.render()}</div>;
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
- ### Custom Guard Functions
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
- You can create custom guard functions:
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
- # Check if user is admin
265
- def isAdmin() -> bool {
266
- if not jacIsLoggedIn() {
267
- return False;
268
- }
269
- user = getCurrentUser(); # Your custom function
270
- return user.role == "admin";
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
- # Check if user has specific permission
274
- def hasPermission(permission: str) -> bool {
275
- if not jacIsLoggedIn() {
276
- return False;
277
- }
278
- user = getCurrentUser();
279
- return permission in user.permissions;
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
- def App() -> any {
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
- settings_route = {
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
- routes = [admin_route, settings_route];
296
- router = initRouter(routes, "/");
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>{router.render()}</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
- ### Redirecting on Guard Failure
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
- You can automatically redirect when a guard fails:
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 protectedGuard() -> bool {
393
+ def Dashboard() -> any {
394
+ # Check if user is logged in
310
395
  if not jacIsLoggedIn() {
311
- navigate("/login"); # Redirect to login
312
- return False;
396
+ return <Navigate to="/login" />;
313
397
  }
314
- return True;
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 App() -> any {
318
- protected_route = {
319
- "path": "/todos",
320
- "component": lambda -> any { return TodoApp(); },
321
- "guard": protectedGuard
322
- };
406
+ def LoginPage() -> any {
407
+ let navigate = useNavigate();
323
408
 
324
- routes = [protected_route];
325
- router = initRouter(routes, "/login");
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 <div>{router.render()}</div>;
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 Example
445
+ ## Complete Examples
335
446
 
336
- Here's a complete routing example from the Todo App:
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 Nav(route: str) -> any {
341
- if not jacIsLoggedIn() or route == "/login" or route == "/signup" {
342
- return None;
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
- return <nav style={{
345
- "background": "#FFFFFF",
346
- "padding": "12px",
347
- "boxShadow": "0 1px 2px rgba(17,24,39,0.06)"
348
- }}>
349
- <div style={{
350
- "maxWidth": "960px",
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 App() -> any {
382
- # Define routes
383
- login_route = {
384
- "path": "/login",
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 jac_app() -> any {
425
- return App();
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
- ## Advanced Patterns
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
- ### Getting Current Route
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
- Use `router.path()` to get the current route:
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 App() -> any {
441
- routes = [/* routes */];
442
- router = initRouter(routes, "/");
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
- currentPath = router.path(); # Get current route
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
- {Nav(currentPath)}
449
- {router.render()}
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
- ### Programmatic Navigation
555
+ ---
456
556
 
457
- Navigate programmatically from anywhere:
557
+ ## Best Practices
458
558
 
559
+ ### 1. **Use Correct Route Syntax**
459
560
  ```jac
460
- cl {
461
- async def handleLogin(e: any) -> None {
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
- def logout_action() -> None {
473
- jacLogout();
474
- navigate("/login"); # Or use router.navigate()
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
- ### Dynamic Route Parameters (Future)
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
- While Jac's router uses hash-based routing without path parameters, you can pass state through navigation:
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
- cl {
485
- def navigateToTodo(id: str) -> None {
486
- # Store ID in state before navigating
487
- setTodoId(id);
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
- ### 404 Handling
605
+ ### 5. **Use Link for Navigation**
606
+ ```jac
607
+ # ✅ CORRECT - Use Link component
608
+ <Link to="/about">About</Link>
500
609
 
501
- The router automatically shows a 404 message if no route matches:
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
- # If user navigates to /unknown-route
505
- # Router automatically renders: "404 - Route not found: /unknown-route"
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
- ## Best Practices
632
+ def isActive(path: str) -> bool {
633
+ return location.pathname == path;
634
+ }
511
635
 
512
- 1. **Default Route**: Always provide a sensible default route in `initRouter()`
513
- 2. **Route Guards**: Use guards for protected routes instead of checking in components
514
- 3. **Link Components**: Use `Link` component instead of manual hash manipulation
515
- 4. **Guard Functions**: Keep guard functions simple and focused
516
- 5. **Navigation**: Use `navigate()` for programmatic navigation
517
- 6. **Current Path**: Use `router.path()` for conditional rendering based on current route
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
- - **Routing**: Use `initRouter()` to create multi-page applications
524
- - **Routes**: Configure routes with `path`, `component`, and optional `guard`
525
- - **Navigation**: Use `Link` component or `navigate()` function
526
- - **Guards**: Protect routes with guard functions
527
- - **Current Route**: Access current route with `router.path()`
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, reactive, and powerful! 🚀
659
+ Routing in Jac is simple, powerful, and production-ready! 🚀
530
660