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,16 +1,19 @@
1
1
  # Lifecycle Hooks in Jac: Component Lifecycle Management
2
2
 
3
- Learn how to use `onMount()` and other lifecycle hooks to manage component initialization, side effects, and cleanup.
3
+ Learn how to use React's `useEffect` and `useState` hooks to manage component state, initialization, side effects, and cleanup.
4
4
 
5
5
  ---
6
6
 
7
7
  ## 📚 Table of Contents
8
8
 
9
9
  - [What are Lifecycle Hooks?](#what-are-lifecycle-hooks)
10
- - [The `onMount()` Hook](#the-onmount-hook)
10
+ - [React Hooks (Recommended)](#react-hooks-recommended)
11
+ - [useState](#usestate)
12
+ - [useEffect](#useeffect)
11
13
  - [Common Use Cases](#common-use-cases)
12
14
  - [Complete Examples](#complete-examples)
13
15
  - [Best Practices](#best-practices)
16
+ - [Legacy Jac Hooks](#legacy-jac-hooks)
14
17
 
15
18
  ---
16
19
 
@@ -21,94 +24,192 @@ Lifecycle hooks are functions that let you run code at specific points in a comp
21
24
  - **When component updates**: React to state changes
22
25
  - **When component unmounts**: Clean up resources
23
26
 
24
- In Jac, the primary lifecycle hook is `onMount()`, which runs code once when a component first renders.
27
+ **Jac uses React hooks as the standard approach:**
28
+ - **useState**: Manage component state
29
+ - **useEffect**: Handle side effects, lifecycle events, and cleanup
25
30
 
26
31
  **Key Benefits:**
27
32
  - **Initialization**: Load data when component appears
28
33
  - **Side Effects**: Set up subscriptions, timers, or listeners
29
- - **One-Time Setup**: Ensure code runs only once, not on every render
30
- - **Component Scoping**: Automatically tracks which component called the hook
34
+ - **Reactive Updates**: Run code when specific dependencies change
35
+ - **Cleanup**: Properly clean up resources when components unmount
36
+ - **Standard React API**: Works exactly like React hooks you already know
31
37
 
32
38
  ---
33
39
 
34
- ## The `onMount()` Hook
40
+ ## React Hooks (Recommended)
35
41
 
36
- ### Basic Usage
42
+ ### useState
43
+
44
+ The `useState` hook lets you add state to your components.
45
+
46
+ #### Basic Usage
47
+
48
+ ```jac
49
+ cl import from react { useState }
50
+
51
+ cl {
52
+ def Counter() -> any {
53
+ let [count, setCount] = useState(0);
54
+
55
+ return <div>
56
+ <h1>Count: {count}</h1>
57
+ <button onClick={lambda e: any -> None {
58
+ setCount(count + 1);
59
+ }}>
60
+ Increment
61
+ </button>
62
+ </div>;
63
+ }
64
+ }
65
+ ```
66
+
67
+ **Key Points:**
68
+ - Import `useState` from `react`
69
+ - Returns an array: `[currentValue, setterFunction]`
70
+ - Use destructuring to get the value and setter
71
+ - Call the setter function to update state
72
+
73
+ #### Multiple State Variables
37
74
 
38
75
  ```jac
76
+ cl import from react { useState }
77
+
78
+ cl {
79
+ def TodoApp() -> any {
80
+ let [todos, setTodos] = useState([]);
81
+ let [inputValue, setInputValue] = useState("");
82
+ let [filter, setFilter] = useState("all");
83
+
84
+ return <div>Todo App</div>;
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### useEffect
90
+
91
+ The `useEffect` hook lets you perform side effects in your components. It provides full lifecycle management including mount, update, and cleanup.
92
+
93
+ #### Basic Usage - Run on Mount
94
+
95
+ ```jac
96
+ cl import from react { useState, useEffect }
97
+
39
98
  cl {
40
99
  def MyComponent() -> any {
41
- onMount(lambda -> None {
100
+ let [data, setData] = useState(None);
101
+
102
+ useEffect(lambda -> None {
42
103
  console.log("Component mounted!");
43
104
  # Load initial data
105
+ async def loadData() -> None {
106
+ result = await jacSpawn("get_data", "", {});
107
+ setData(result);
108
+ }
44
109
  loadData();
45
- });
110
+ }, []); # Empty array means run only on mount
46
111
 
47
112
  return <div>My Component</div>;
48
113
  }
49
114
  }
50
115
  ```
51
116
 
52
- **How it Works:**
53
- 1. Component renders
54
- 2. `onMount()` is called
55
- 3. The provided function runs **once** after the component is fully rendered
56
- 4. Function won't run again on re-renders
117
+ **Key Points:**
118
+ - Import `useEffect` from `react`
119
+ - First argument: function to run
120
+ - Second argument: dependency array
121
+ - `[]` - run only on mount
122
+ - `[count]` - run when `count` changes
123
+ - No array - run on every render
57
124
 
58
- ### Key Characteristics
125
+ #### useEffect with Dependencies
59
126
 
60
- - **Runs Once**: Executes only on the first render
61
- - **After Render**: Runs after the component is mounted to the DOM
62
- - **Component Scoped**: Automatically tracks which component called it
63
- - **Async Support**: Can contain async operations
127
+ ```jac
128
+ cl import from react { useState, useEffect }
64
129
 
65
- ---
130
+ cl {
131
+ def Counter() -> any {
132
+ let [count, setCount] = useState(0);
66
133
 
67
- ## Common Use Cases
134
+ useEffect(lambda -> None {
135
+ console.log("Count changed to:", count);
136
+ document.title = "Count: " + str(count);
137
+ }, [count]); # Run when count changes
68
138
 
69
- ### 1. Loading Initial Data
139
+ return <div>
140
+ <h1>Count: {count}</h1>
141
+ <button onClick={lambda e: any -> None {
142
+ setCount(count + 1);
143
+ }}>
144
+ Increment
145
+ </button>
146
+ </div>;
147
+ }
148
+ }
149
+ ```
70
150
 
71
- The most common use case is loading data when a component mounts:
151
+ #### useEffect with Cleanup
72
152
 
73
153
  ```jac
154
+ cl import from react { useEffect }
155
+
74
156
  cl {
75
- let [todoState, setTodoState] = createState({
76
- "items": [],
77
- "loading": True
78
- });
157
+ def TimerComponent() -> any {
158
+ useEffect(lambda -> any {
159
+ # Setup
160
+ intervalId = setInterval(lambda -> None {
161
+ console.log("Timer tick");
162
+ }, 1000);
163
+
164
+ # Cleanup function (returned from useEffect)
165
+ return lambda -> None {
166
+ clearInterval(intervalId);
167
+ };
168
+ }, []);
169
+
170
+ return <div>Timer Component</div>;
171
+ }
172
+ }
173
+ ```
79
174
 
80
- async def loadTodos() -> None {
81
- setTodoState({"loading": True});
175
+ ---
82
176
 
83
- # Fetch todos from backend
84
- todos = await __jacSpawn("read_todos");
177
+ ## Common Use Cases
85
178
 
86
- items = [];
87
- for todo in todos.reports {
88
- items.push({
89
- "id": todo._jac_id,
90
- "text": todo.text,
91
- "done": todo.done
92
- });
93
- }
179
+ ### 1. Loading Initial Data
94
180
 
95
- setTodoState({"items": items, "loading": False});
96
- }
181
+ The most common use case is loading data when a component mounts:
182
+
183
+ ```jac
184
+ cl import from react { useState, useEffect }
185
+ cl import from '@jac-client/utils' { jacSpawn }
97
186
 
187
+ cl {
98
188
  def TodoApp() -> any {
99
- # Load todos when component mounts
100
- onMount(lambda -> None {
189
+ let [todos, setTodos] = useState([]);
190
+ let [loading, setLoading] = useState(True);
191
+
192
+ useEffect(lambda -> None {
193
+ async def loadTodos() -> None {
194
+ setLoading(True);
195
+
196
+ # Fetch todos from backend
197
+ result = await jacSpawn("read_todos", "", {});
198
+ console.log(result);
199
+ setTodos(result.reports);
200
+ setLoading(False);
201
+ }
101
202
  loadTodos();
102
- });
203
+ }, []); # Empty array = run only on mount
103
204
 
104
- s = todoState();
105
-
106
- if s.loading {
205
+ if loading {
107
206
  return <div>Loading...</div>;
108
207
  }
109
208
 
110
209
  return <div>
111
- {[TodoItem(item) for item in s.items]}
210
+ {todos.map(lambda todo: any -> any {
211
+ return <TodoItem todo={todo} />;
212
+ })}
112
213
  </div>;
113
214
  }
114
215
  }
@@ -116,39 +217,37 @@ cl {
116
217
 
117
218
  ### 2. Setting Up Event Listeners
118
219
 
119
- Set up event listeners that need to persist across re-renders:
220
+ Set up event listeners with proper cleanup:
120
221
 
121
222
  ```jac
223
+ cl import from react { useState, useEffect }
224
+
122
225
  cl {
123
226
  def WindowResizeHandler() -> any {
124
- let [windowSize, setWindowSize] = createState({
125
- "width": 0,
126
- "height": 0
127
- });
128
-
129
- def handleResize() -> None {
130
- setWindowSize({
131
- "width": window.innerWidth,
132
- "height": window.innerHeight
133
- });
134
- }
227
+ let [width, setWidth] = useState(0);
228
+ let [height, setHeight] = useState(0);
135
229
 
136
- def ResizeDisplay() -> any {
137
- onMount(lambda -> None {
138
- # Set initial size
139
- handleResize();
230
+ useEffect(lambda -> any {
231
+ def handleResize() -> None {
232
+ setWidth(window.innerWidth);
233
+ setHeight(window.innerHeight);
234
+ }
140
235
 
141
- # Add listener
142
- window.addEventListener("resize", handleResize);
143
- });
236
+ # Set initial size
237
+ handleResize();
144
238
 
145
- s = windowSize();
146
- return <div>
147
- Window size: {s.width} x {s.height}
148
- </div>;
149
- }
239
+ # Add listener
240
+ window.addEventListener("resize", handleResize);
241
+
242
+ # Cleanup function
243
+ return lambda -> None {
244
+ window.removeEventListener("resize", handleResize);
245
+ };
246
+ }, []);
150
247
 
151
- return ResizeDisplay();
248
+ return <div>
249
+ Window size: {width} x {height}
250
+ </div>;
152
251
  }
153
252
  }
154
253
  ```
@@ -158,45 +257,40 @@ cl {
158
257
  Load user-specific data when a component mounts:
159
258
 
160
259
  ```jac
161
- cl {
162
- let [userState, setUserState] = createState({
163
- "profile": None,
164
- "loading": True
165
- });
166
-
167
- async def loadUserProfile() -> None {
168
- if not jacIsLoggedIn() {
169
- navigate("/login");
170
- return;
171
- }
172
-
173
- # Fetch user profile
174
- profile = await __jacSpawn("get_user_profile");
175
-
176
- setUserState({
177
- "profile": profile,
178
- "loading": False
179
- });
180
- }
260
+ cl import from react { useState, useEffect }
261
+ cl import from '@jac-client/utils' { jacSpawn }
181
262
 
263
+ cl {
182
264
  def ProfileView() -> any {
183
- onMount(lambda -> None {
265
+ let [profile, setProfile] = useState(None);
266
+ let [loading, setLoading] = useState(True);
267
+
268
+ useEffect(lambda -> None {
269
+ async def loadUserProfile() -> None {
270
+ if not jacIsLoggedIn() {
271
+ navigate("/login");
272
+ return;
273
+ }
274
+
275
+ # Fetch user profile
276
+ result = await jacSpawn("get_user_profile", "", {});
277
+ setProfile(result);
278
+ setLoading(False);
279
+ }
184
280
  loadUserProfile();
185
- });
186
-
187
- s = userState();
281
+ }, []);
188
282
 
189
- if s.loading {
283
+ if loading {
190
284
  return <div>Loading profile...</div>;
191
285
  }
192
286
 
193
- if not s.profile {
287
+ if not profile {
194
288
  return <div>No profile found</div>;
195
289
  }
196
290
 
197
291
  return <div>
198
- <h1>{s.profile.username}</h1>
199
- <p>{s.profile.email}</p>
292
+ <h1>{profile.username}</h1>
293
+ <p>{profile.email}</p>
200
294
  </div>;
201
295
  }
202
296
  }
@@ -207,16 +301,23 @@ cl {
207
301
  Initialize external libraries or APIs:
208
302
 
209
303
  ```jac
304
+ cl import from react { useEffect }
305
+
210
306
  cl {
211
307
  def ChartComponent() -> any {
212
- onMount(lambda -> None {
308
+ useEffect(lambda -> any {
213
309
  # Initialize chart library
214
310
  chart = new Chart("myChart", {
215
311
  "type": "line",
216
312
  "data": chartData,
217
313
  "options": chartOptions
218
314
  });
219
- });
315
+
316
+ # Cleanup function
317
+ return lambda -> None {
318
+ chart.destroy();
319
+ };
320
+ }, []);
220
321
 
221
322
  return <canvas id="myChart"></canvas>;
222
323
  }
@@ -228,15 +329,17 @@ cl {
228
329
  Focus an input field when a component mounts:
229
330
 
230
331
  ```jac
332
+ cl import from react { useEffect }
333
+
231
334
  cl {
232
335
  def SearchBar() -> any {
233
- onMount(lambda -> None {
336
+ useEffect(lambda -> None {
234
337
  # Focus search input on mount
235
338
  inputEl = document.getElementById("search-input");
236
339
  if inputEl {
237
340
  inputEl.focus();
238
341
  }
239
- });
342
+ }, []);
240
343
 
241
344
  return <input
242
345
  id="search-input"
@@ -254,65 +357,112 @@ cl {
254
357
  ### Example 1: Todo App with Data Loading
255
358
 
256
359
  ```jac
257
- cl {
258
- let [todoState, setTodoState] = createState({
259
- "items": [],
260
- "filter": "all",
261
- "loading": False
262
- });
263
-
264
- async def read_todos_action() -> None {
265
- setTodoState({"loading": True});
360
+ cl import from react { useState, useEffect }
361
+ cl import from '@jac-client/utils' { jacSpawn }
266
362
 
267
- try {
268
- todos = await __jacSpawn("read_todos");
269
-
270
- items = [];
271
- for todo in todos.reports {
272
- items.push({
273
- "id": todo._jac_id,
274
- "text": todo.text,
275
- "done": todo.done
276
- });
363
+ cl {
364
+ def app() -> any {
365
+ let [todos, setTodos] = useState([]);
366
+ let [inputValue, setInputValue] = useState("");
367
+ let [filter, setFilter] = useState("all");
368
+
369
+ useEffect(lambda -> None {
370
+ async def loadTodos() -> None {
371
+ todos = await jacSpawn("read_todos","",{});
372
+ console.log(todos);
373
+ setTodos(todos.reports);
277
374
  }
278
-
279
- setTodoState({"items": items, "loading": False});
280
- } except Exception as err {
281
- console.error("Failed to load todos:", err);
282
- setTodoState({"loading": False});
375
+ loadTodos();
376
+ }, []);
377
+
378
+ # Add a new todo
379
+ async def addTodo() -> None {
380
+ if not inputValue.trim() { return; }
381
+ newTodo = {
382
+ "id": Date.now(),
383
+ "text": inputValue.trim(),
384
+ "done": False
385
+ };
386
+ await jacSpawn("create_todo","", {"text": inputValue.trim()});
387
+ newTodos = todos.concat([newTodo]);
388
+ setTodos(newTodos);
389
+ setInputValue("");
283
390
  }
284
- }
285
-
286
- def TodoApp() -> any {
287
- # Load todos when component mounts
288
- onMount(lambda -> None {
289
- read_todos_action();
290
- });
291
-
292
- s = todoState();
293
391
 
294
- if s.loading {
295
- return <div style={{
296
- "textAlign": "center",
297
- "padding": "40px"
298
- }}>
299
- Loading todos...
300
- </div>;
392
+ # Toggle todo completion status
393
+ async def toggleTodo(id: any) -> None {
394
+ await jacSpawn("toggle_todo",id, {});
395
+ setTodos(todos.map(lambda todo: any -> any {
396
+ if todo._jac_id == id {
397
+ updatedTodo = {
398
+ "_jac_id": todo._jac_id,
399
+ "text": todo.text,
400
+ "done": not todo.done,
401
+ "id": todo.id
402
+ };
403
+ return updatedTodo;
404
+ }
405
+ return todo;
406
+ }));
301
407
  }
302
408
 
303
- itemsArr = filteredItems();
304
- children = [];
305
- for it in itemsArr {
306
- children.push(TodoItem(it));
409
+ # Filter todos based on current filter
410
+ def getFilteredTodos() -> list {
411
+ if filter == "active" {
412
+ return todos.filter(lambda todo: any -> bool { return not todo.done; });
413
+ } elif filter == "completed" {
414
+ return todos.filter(lambda todo: any -> bool { return todo.done; });
415
+ }
416
+ return todos;
307
417
  }
308
418
 
309
- return <div>
310
- <h2>My Todos</h2>
311
- <form onSubmit={onAddTodo}>
312
- <input id="todo-input" type="text" />
313
- <button type="submit">Add Todo</button>
314
- </form>
315
- <ul>{children}</ul>
419
+ filteredTodos = getFilteredTodos();
420
+
421
+ return <div style={{
422
+ "maxWidth": "600px",
423
+ "margin": "40px auto",
424
+ "padding": "24px",
425
+ "fontFamily": "system-ui, -apple-system, sans-serif"
426
+ }}>
427
+ <h1 style={{"textAlign": "center"}}>📝 My Todo App</h1>
428
+
429
+ # Add todo form
430
+ <div style={{"display": "flex", "gap": "8px", "marginBottom": "24px"}}>
431
+ <input
432
+ type="text"
433
+ value={inputValue}
434
+ onChange={lambda e: any -> None { setInputValue(e.target.value); }}
435
+ onKeyPress={lambda e: any -> None {
436
+ if e.key == "Enter" { addTodo(); }
437
+ }}
438
+ placeholder="What needs to be done?"
439
+ style={{"flex": "1", "padding": "12px"}}
440
+ />
441
+ <button onClick={addTodo} style={{"padding": "12px 24px"}}>
442
+ Add
443
+ </button>
444
+ </div>
445
+
446
+ # Filter buttons
447
+ <div style={{"display": "flex", "gap": "8px", "marginBottom": "16px"}}>
448
+ <button onClick={lambda -> None { setFilter("all"); }}>All</button>
449
+ <button onClick={lambda -> None { setFilter("active"); }}>Active</button>
450
+ <button onClick={lambda -> None { setFilter("completed"); }}>Completed</button>
451
+ </div>
452
+
453
+ # Todo list
454
+ <ul>
455
+ {filteredTodos.map(lambda todo: any -> any {
456
+ return <li key={todo._jac_id}>
457
+ <input
458
+ type="checkbox"
459
+ checked={todo.done}
460
+ onChange={lambda -> None { toggleTodo(todo._jac_id); }}
461
+ />
462
+ <span>{todo.text}</span>
463
+ </li>;
464
+ })}
465
+ </ul>
316
466
  </div>;
317
467
  }
318
468
  }
@@ -321,71 +471,68 @@ cl {
321
471
  ### Example 2: Dashboard with Multiple Data Sources
322
472
 
323
473
  ```jac
324
- cl {
325
- let [dashboardState, setDashboardState] = createState({
326
- "stats": None,
327
- "recentActivity": [],
328
- "loading": True
329
- });
330
-
331
- async def loadDashboardData() -> None {
332
- setDashboardState({"loading": True});
333
-
334
- # Load multiple data sources in parallel
335
- [stats, activity] = await Promise.all([
336
- __jacSpawn("get_stats"),
337
- __jacSpawn("get_recent_activity")
338
- ]);
339
-
340
- setDashboardState({
341
- "stats": stats,
342
- "recentActivity": activity.reports,
343
- "loading": False
344
- });
345
- }
474
+ cl import from react { useState, useEffect }
475
+ cl import from '@jac-client/utils' { jacSpawn }
346
476
 
477
+ cl {
347
478
  def Dashboard() -> any {
348
- # Load all dashboard data on mount
349
- onMount(lambda -> None {
479
+ let [stats, setStats] = useState(None);
480
+ let [activity, setActivity] = useState([]);
481
+ let [loading, setLoading] = useState(True);
482
+
483
+ useEffect(lambda -> None {
484
+ async def loadDashboardData() -> None {
485
+ setLoading(True);
486
+
487
+ # Load multiple data sources in parallel
488
+ results = await Promise.all([
489
+ jacSpawn("get_stats", "", {}),
490
+ jacSpawn("get_recent_activity", "", {})
491
+ ]);
492
+
493
+ setStats(results[0]);
494
+ setActivity(results[1].reports);
495
+ setLoading(False);
496
+ }
350
497
  loadDashboardData();
351
- });
498
+ }, []);
352
499
 
353
- s = dashboardState();
354
-
355
- if s.loading {
500
+ if loading {
356
501
  return <div>Loading dashboard...</div>;
357
502
  }
358
503
 
359
504
  return <div>
360
- <StatsView stats={s.stats} />
361
- <ActivityList activities={s.recentActivity} />
505
+ <StatsView stats={stats} />
506
+ <ActivityList activities={activity} />
362
507
  </div>;
363
508
  }
364
509
  }
365
510
  ```
366
511
 
367
- ### Example 3: Component with Cleanup (Future)
512
+ ### Example 3: Timer Component with Cleanup
368
513
 
369
- While `onMount()` doesn't currently support cleanup directly, you can store cleanup functions:
514
+ Proper cleanup when component unmounts:
370
515
 
371
516
  ```jac
372
- cl {
373
- let cleanupFunctions = [];
517
+ cl import from react { useState, useEffect }
374
518
 
519
+ cl {
375
520
  def TimerComponent() -> any {
376
- onMount(lambda -> None {
521
+ let [seconds, setSeconds] = useState(0);
522
+
523
+ useEffect(lambda -> any {
377
524
  # Set up timer
378
525
  intervalId = setInterval(lambda -> None {
379
- console.log("Timer tick");
526
+ setSeconds(lambda prev: int -> int { return prev + 1; });
380
527
  }, 1000);
381
528
 
382
- # Store cleanup function
383
- cleanupFunctions.push(lambda -> None {
529
+ # Cleanup function - runs when component unmounts
530
+ return lambda -> None {
384
531
  clearInterval(intervalId);
385
- });
386
- });
532
+ };
533
+ }, []);
387
534
 
388
- return <div>Timer Component</div>;
535
+ return <div>Timer: {seconds} seconds</div>;
389
536
  }
390
537
  }
391
538
  ```
@@ -394,161 +541,234 @@ cl {
394
541
 
395
542
  ## Best Practices
396
543
 
397
- ### 1. Use `onMount()` for Initialization Only
544
+ ### 1. Always Specify Dependencies
545
+
546
+ Be explicit about what your effect depends on:
398
547
 
399
548
  ```jac
400
- # ✅ Good: One-time initialization
401
- def Component() -> any {
402
- onMount(lambda -> None {
403
- loadInitialData();
404
- });
405
- return <div>Component</div>;
406
- }
549
+ # ✅ Good: Empty array for mount-only effects
550
+ useEffect(lambda -> None {
551
+ loadInitialData();
552
+ }, []);
553
+
554
+ # ✅ Good: Specify dependencies
555
+ useEffect(lambda -> None {
556
+ console.log("Count changed:", count);
557
+ }, [count]);
558
+
559
+ # ⚠️ Warning: No dependency array runs on every render
560
+ useEffect(lambda -> None {
561
+ console.log("Runs on every render!");
562
+ });
563
+ ```
407
564
 
408
- # Avoid: Side effects that should run on every state change
409
- def Component() -> any {
410
- s = state();
411
- onMount(lambda -> None {
412
- # This won't run when state changes!
413
- processState(s);
414
- });
415
- return <div>{s.value}</div>;
416
- }
565
+ ### 2. Handle Async Operations Properly
566
+
567
+ Always handle async operations with proper error handling:
568
+
569
+ ```jac
570
+ # ✅ Good: Proper async handling
571
+ useEffect(lambda -> None {
572
+ async def loadData() -> None {
573
+ try {
574
+ data = await jacSpawn("get_data", "", {});
575
+ setData(data);
576
+ } except Exception as err {
577
+ console.error("Error loading data:", err);
578
+ setError(err);
579
+ }
580
+ }
581
+ loadData();
582
+ }, []);
417
583
  ```
418
584
 
419
- ### 2. Handle Async Operations
585
+ ### 3. Clean Up Side Effects
420
586
 
421
- Always handle async operations properly:
587
+ Always clean up event listeners, timers, and subscriptions:
422
588
 
423
589
  ```jac
424
- # ✅ Good: Handle async properly
425
- async def loadData() -> None {
426
- try {
427
- data = await __jacSpawn("get_data");
428
- setState({"data": data});
429
- } except Exception as err {
430
- console.error("Error loading data:", err);
590
+ # ✅ Good: Cleanup function removes event listener
591
+ useEffect(lambda -> any {
592
+ def handleResize() -> None {
593
+ setWidth(window.innerWidth);
431
594
  }
432
- }
433
595
 
434
- def Component() -> any {
435
- onMount(lambda -> None {
436
- loadData();
437
- });
438
- return <div>Component</div>;
439
- }
596
+ window.addEventListener("resize", handleResize);
597
+
598
+ return lambda -> None {
599
+ window.removeEventListener("resize", handleResize);
600
+ };
601
+ }, []);
440
602
  ```
441
603
 
442
- ### 3. Check Conditions Before Operations
604
+ ### 4. Use Loading States
443
605
 
444
- Verify prerequisites before executing operations:
606
+ Show loading indicators while data is being fetched:
445
607
 
446
608
  ```jac
447
- # ✅ Good: Check authentication first
448
- def ProtectedComponent() -> any {
449
- onMount(lambda -> None {
450
- if not jacIsLoggedIn() {
451
- navigate("/login");
452
- return;
609
+ # ✅ Good: Clear loading states
610
+ def Component() -> any {
611
+ let [data, setData] = useState(None);
612
+ let [loading, setLoading] = useState(True);
613
+ let [error, setError] = useState(None);
614
+
615
+ useEffect(lambda -> None {
616
+ async def loadData() -> None {
617
+ try {
618
+ setLoading(True);
619
+ result = await jacSpawn("get_data", "", {});
620
+ setData(result);
621
+ } except Exception as err {
622
+ setError(err);
623
+ } finally {
624
+ setLoading(False);
625
+ }
453
626
  }
454
- loadUserData();
455
- });
456
- return <div>Protected Content</div>;
627
+ loadData();
628
+ }, []);
629
+
630
+ if loading { return <div>Loading...</div>; }
631
+ if error { return <div>Error: {error}</div>; }
632
+ return <div>{data}</div>;
457
633
  }
458
634
  ```
459
635
 
460
- ### 4. Use Loading States
636
+ ### 5. Keep Effects Focused
461
637
 
462
- Show loading indicators while data is being fetched:
638
+ Each effect should have a single responsibility:
463
639
 
464
640
  ```jac
641
+ # ✅ Good: Separate effects for separate concerns
465
642
  def Component() -> any {
466
- let [dataState, setDataState] = createState({
467
- "data": None,
468
- "loading": True
469
- });
643
+ useEffect(lambda -> None {
644
+ loadData(); # Data loading
645
+ }, []);
646
+
647
+ useEffect(lambda -> any {
648
+ # Event listener setup
649
+ window.addEventListener("resize", handleResize);
650
+ return lambda -> None {
651
+ window.removeEventListener("resize", handleResize);
652
+ };
653
+ }, []);
470
654
 
471
- onMount(lambda -> None {
472
- loadData();
473
- });
655
+ return <div>Component</div>;
656
+ }
657
+ ```
474
658
 
475
- s = dataState();
476
- if s.loading {
477
- return <div>Loading...</div>;
478
- }
659
+ ### 6. Avoid Stale Closures
479
660
 
480
- return <div>{s.data}</div>;
481
- }
661
+ Be careful with closures capturing old state values:
662
+
663
+ ```jac
664
+ # ❌ Avoid: Stale closure problem
665
+ useEffect(lambda -> None {
666
+ setInterval(lambda -> None {
667
+ setCount(count + 1); # count is stale!
668
+ }, 1000);
669
+ }, []);
670
+
671
+ # ✅ Good: Use functional update
672
+ useEffect(lambda -> any {
673
+ intervalId = setInterval(lambda -> None {
674
+ setCount(lambda prev: int -> int { return prev + 1; });
675
+ }, 1000);
676
+
677
+ return lambda -> None {
678
+ clearInterval(intervalId);
679
+ };
680
+ }, []);
482
681
  ```
483
682
 
484
- ### 5. Avoid Multiple `onMount()` Calls
683
+ ---
684
+
685
+ ## Summary
485
686
 
486
- Group initialization logic in a single `onMount()` call:
687
+ - **useState**: Manage component state (replaces `createState()`, `createSignal()`)
688
+ - **useEffect**: Handle side effects and lifecycle events (replaces `onMount()`, `createEffect()`)
689
+ - **Dependencies**: Always specify what your effect depends on
690
+ - **Cleanup**: Return a cleanup function for subscriptions, timers, and listeners
691
+ - **Best Practices**: Handle errors, use loading states, keep effects focused
692
+
693
+ React hooks provide a powerful and standard way to manage component lifecycle! 🎯
694
+
695
+ ---
696
+
697
+ ## Legacy Jac Hooks
698
+
699
+ > **Note**: The following hooks are from older Jac versions. New projects should use React hooks instead.
700
+
701
+ ### `onMount()` - Legacy
702
+
703
+ The `onMount()` hook was a Jac-specific hook for running code once when a component mounts:
487
704
 
488
705
  ```jac
489
- # Good: Single onMount with multiple operations
706
+ # Legacy approach - use useEffect instead
490
707
  def Component() -> any {
491
708
  onMount(lambda -> None {
492
709
  loadData();
493
- setupEventListeners();
494
- initializeThirdParty();
495
710
  });
496
711
  return <div>Component</div>;
497
712
  }
713
+ ```
714
+
715
+ **Modern equivalent:**
498
716
 
499
- # ❌ Avoid: Multiple onMount calls
717
+ ```jac
718
+ # Modern approach with React hooks
500
719
  def Component() -> any {
501
- onMount(lambda -> None { loadData(); });
502
- onMount(lambda -> None { setupEventListeners(); });
503
- onMount(lambda -> None { initializeThirdParty(); });
720
+ useEffect(lambda -> None {
721
+ loadData();
722
+ }, []);
504
723
  return <div>Component</div>;
505
724
  }
506
725
  ```
507
726
 
508
- ---
509
-
510
- ## When NOT to Use `onMount()`
727
+ ### `createState()` - Legacy
511
728
 
512
- ### Use `createEffect()` for Reactive Side Effects
513
-
514
- If you need code to run when state changes, use `createEffect()` instead:
729
+ The `createState()` hook was a Jac-specific state management solution:
515
730
 
516
731
  ```jac
517
- # Use createEffect() for state-dependent side effects
732
+ # Legacy approach - use useState instead
733
+ let [state, setState] = createState({"count": 0});
734
+
518
735
  def Component() -> any {
519
- let [count, setCount] = createSignal(0);
736
+ s = state();
737
+ return <div>{s.count}</div>;
738
+ }
739
+ ```
520
740
 
521
- createEffect(lambda -> None {
522
- console.log("Count changed:", count());
523
- });
741
+ **Modern equivalent:**
524
742
 
525
- return <div>
526
- <button onClick={lambda -> None { setCount(count() + 1); }}>
527
- Count: {count()}
528
- </button>
529
- </div>;
743
+ ```jac
744
+ # Modern approach with React hooks
745
+ def Component() -> any {
746
+ let [count, setCount] = useState(0);
747
+ return <div>{count}</div>;
530
748
  }
749
+ ```
531
750
 
532
- # ❌ onMount() won't run when state changes
533
- def Component() -> any {
534
- let [count, setCount] = createSignal(0);
751
+ ### `createSignal()` and `createEffect()` - Legacy
535
752
 
536
- onMount(lambda -> None {
537
- console.log("Count:", count()); # Only logs initial value
538
- });
753
+ These were Signal-based reactive primitives from Jac:
539
754
 
540
- return <div>Component</div>;
541
- }
542
- ```
755
+ ```jac
756
+ # Legacy approach
757
+ let [count, setCount] = createSignal(0);
543
758
 
544
- ---
759
+ createEffect(lambda -> None {
760
+ console.log("Count:", count());
761
+ });
762
+ ```
545
763
 
546
- ## Summary
764
+ **Modern equivalent:**
547
765
 
548
- - **`onMount()`**: Runs code once when a component first mounts
549
- - **Use Cases**: Loading data, setting up listeners, initializing libraries
550
- - **Best Practices**: Handle async properly, check conditions, show loading states
551
- - **Avoid**: Using `onMount()` for reactive side effects (use `createEffect()` instead)
766
+ ```jac
767
+ # Modern approach with React hooks
768
+ let [count, setCount] = useState(0);
552
769
 
553
- Lifecycle hooks help you manage component initialization and side effects effectively! 🎯
770
+ useEffect(lambda -> None {
771
+ console.log("Count:", count);
772
+ }, [count]);
773
+ ```
554
774