jac-client 0.2.2__py3-none-any.whl → 0.2.4__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 (176) hide show
  1. jac_client/examples/all-in-one/assets/workers/worker.py +5 -0
  2. jac_client/tests/conftest.py +281 -0
  3. jac_client/tests/test_cli.py +755 -0
  4. jac_client/tests/test_it.py +347 -67
  5. {jac_client-0.2.2.dist-info → jac_client-0.2.4.dist-info}/METADATA +30 -24
  6. jac_client-0.2.4.dist-info/RECORD +10 -0
  7. {jac_client-0.2.2.dist-info → jac_client-0.2.4.dist-info}/WHEEL +2 -1
  8. jac_client-0.2.4.dist-info/entry_points.txt +4 -0
  9. jac_client-0.2.4.dist-info/top_level.txt +1 -0
  10. jac_client/docs/README.md +0 -689
  11. jac_client/docs/advanced-state.md +0 -1265
  12. jac_client/docs/asset-serving/intro.md +0 -209
  13. jac_client/docs/assets/pipe_line-v2.svg +0 -32
  14. jac_client/docs/assets/pipe_line.png +0 -0
  15. jac_client/docs/file-system/app.jac.md +0 -121
  16. jac_client/docs/file-system/backend-frontend.md +0 -217
  17. jac_client/docs/file-system/intro.md +0 -72
  18. jac_client/docs/file-system/nested-imports.md +0 -348
  19. jac_client/docs/guide-example/intro.md +0 -115
  20. jac_client/docs/guide-example/step-01-setup.md +0 -270
  21. jac_client/docs/guide-example/step-02-components.md +0 -416
  22. jac_client/docs/guide-example/step-03-styling.md +0 -478
  23. jac_client/docs/guide-example/step-04-todo-ui.md +0 -477
  24. jac_client/docs/guide-example/step-05-local-state.md +0 -530
  25. jac_client/docs/guide-example/step-06-events.md +0 -749
  26. jac_client/docs/guide-example/step-07-effects.md +0 -468
  27. jac_client/docs/guide-example/step-08-walkers.md +0 -534
  28. jac_client/docs/guide-example/step-09-authentication.md +0 -586
  29. jac_client/docs/guide-example/step-10-routing.md +0 -539
  30. jac_client/docs/guide-example/step-11-final.md +0 -963
  31. jac_client/docs/imports.md +0 -1141
  32. jac_client/docs/lifecycle-hooks.md +0 -773
  33. jac_client/docs/routing.md +0 -659
  34. jac_client/docs/styling/intro.md +0 -249
  35. jac_client/docs/styling/js-styling.md +0 -367
  36. jac_client/docs/styling/material-ui.md +0 -341
  37. jac_client/docs/styling/pure-css.md +0 -299
  38. jac_client/docs/styling/sass.md +0 -403
  39. jac_client/docs/styling/styled-components.md +0 -395
  40. jac_client/docs/styling/tailwind.md +0 -298
  41. jac_client/examples/all-in-one/.babelrc +0 -9
  42. jac_client/examples/all-in-one/README.md +0 -16
  43. jac_client/examples/all-in-one/app.jac +0 -426
  44. jac_client/examples/all-in-one/assets/burger.png +0 -0
  45. jac_client/examples/all-in-one/button.jac +0 -7
  46. jac_client/examples/all-in-one/components/button.jac +0 -7
  47. jac_client/examples/all-in-one/package.json +0 -29
  48. jac_client/examples/all-in-one/styles.css +0 -26
  49. jac_client/examples/all-in-one/vite.config.js +0 -28
  50. jac_client/examples/asset-serving/css-with-image/.babelrc +0 -9
  51. jac_client/examples/asset-serving/css-with-image/README.md +0 -91
  52. jac_client/examples/asset-serving/css-with-image/app.jac +0 -88
  53. jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
  54. jac_client/examples/asset-serving/css-with-image/package.json +0 -28
  55. jac_client/examples/asset-serving/css-with-image/styles.css +0 -26
  56. jac_client/examples/asset-serving/css-with-image/vite.config.js +0 -28
  57. jac_client/examples/asset-serving/image-asset/.babelrc +0 -9
  58. jac_client/examples/asset-serving/image-asset/README.md +0 -119
  59. jac_client/examples/asset-serving/image-asset/app.jac +0 -55
  60. jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
  61. jac_client/examples/asset-serving/image-asset/package.json +0 -28
  62. jac_client/examples/asset-serving/image-asset/styles.css +0 -26
  63. jac_client/examples/asset-serving/image-asset/vite.config.js +0 -28
  64. jac_client/examples/asset-serving/import-alias/.babelrc +0 -9
  65. jac_client/examples/asset-serving/import-alias/README.md +0 -83
  66. jac_client/examples/asset-serving/import-alias/app.jac +0 -111
  67. jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
  68. jac_client/examples/asset-serving/import-alias/package.json +0 -28
  69. jac_client/examples/asset-serving/import-alias/vite.config.js +0 -28
  70. jac_client/examples/basic/.babelrc +0 -9
  71. jac_client/examples/basic/README.md +0 -16
  72. jac_client/examples/basic/app.jac +0 -21
  73. jac_client/examples/basic/package.json +0 -27
  74. jac_client/examples/basic/vite.config.js +0 -27
  75. jac_client/examples/basic-auth/.babelrc +0 -9
  76. jac_client/examples/basic-auth/README.md +0 -16
  77. jac_client/examples/basic-auth/app.jac +0 -308
  78. jac_client/examples/basic-auth/package.json +0 -27
  79. jac_client/examples/basic-auth/vite.config.js +0 -27
  80. jac_client/examples/basic-auth-with-router/.babelrc +0 -9
  81. jac_client/examples/basic-auth-with-router/README.md +0 -60
  82. jac_client/examples/basic-auth-with-router/app.jac +0 -464
  83. jac_client/examples/basic-auth-with-router/package.json +0 -28
  84. jac_client/examples/basic-auth-with-router/vite.config.js +0 -27
  85. jac_client/examples/basic-full-stack/.babelrc +0 -9
  86. jac_client/examples/basic-full-stack/README.md +0 -18
  87. jac_client/examples/basic-full-stack/app.jac +0 -320
  88. jac_client/examples/basic-full-stack/package.json +0 -28
  89. jac_client/examples/basic-full-stack/vite.config.js +0 -27
  90. jac_client/examples/css-styling/js-styling/.babelrc +0 -9
  91. jac_client/examples/css-styling/js-styling/README.md +0 -183
  92. jac_client/examples/css-styling/js-styling/app.jac +0 -84
  93. jac_client/examples/css-styling/js-styling/package.json +0 -28
  94. jac_client/examples/css-styling/js-styling/styles.js +0 -100
  95. jac_client/examples/css-styling/js-styling/vite.config.js +0 -27
  96. jac_client/examples/css-styling/material-ui/.babelrc +0 -9
  97. jac_client/examples/css-styling/material-ui/README.md +0 -16
  98. jac_client/examples/css-styling/material-ui/app.jac +0 -122
  99. jac_client/examples/css-styling/material-ui/package.json +0 -32
  100. jac_client/examples/css-styling/material-ui/vite.config.js +0 -27
  101. jac_client/examples/css-styling/pure-css/.babelrc +0 -9
  102. jac_client/examples/css-styling/pure-css/README.md +0 -16
  103. jac_client/examples/css-styling/pure-css/app.jac +0 -64
  104. jac_client/examples/css-styling/pure-css/package.json +0 -28
  105. jac_client/examples/css-styling/pure-css/styles.css +0 -111
  106. jac_client/examples/css-styling/pure-css/vite.config.js +0 -27
  107. jac_client/examples/css-styling/sass-example/.babelrc +0 -9
  108. jac_client/examples/css-styling/sass-example/README.md +0 -16
  109. jac_client/examples/css-styling/sass-example/app.jac +0 -64
  110. jac_client/examples/css-styling/sass-example/package.json +0 -29
  111. jac_client/examples/css-styling/sass-example/styles.scss +0 -153
  112. jac_client/examples/css-styling/sass-example/vite.config.js +0 -27
  113. jac_client/examples/css-styling/styled-components/.babelrc +0 -9
  114. jac_client/examples/css-styling/styled-components/README.md +0 -16
  115. jac_client/examples/css-styling/styled-components/app.jac +0 -71
  116. jac_client/examples/css-styling/styled-components/package.json +0 -29
  117. jac_client/examples/css-styling/styled-components/styled.js +0 -90
  118. jac_client/examples/css-styling/styled-components/vite.config.js +0 -27
  119. jac_client/examples/css-styling/tailwind-example/.babelrc +0 -9
  120. jac_client/examples/css-styling/tailwind-example/README.md +0 -16
  121. jac_client/examples/css-styling/tailwind-example/app.jac +0 -63
  122. jac_client/examples/css-styling/tailwind-example/global.css +0 -1
  123. jac_client/examples/css-styling/tailwind-example/package.json +0 -30
  124. jac_client/examples/css-styling/tailwind-example/vite.config.js +0 -29
  125. jac_client/examples/full-stack-with-auth/.babelrc +0 -9
  126. jac_client/examples/full-stack-with-auth/README.md +0 -16
  127. jac_client/examples/full-stack-with-auth/app.jac +0 -722
  128. jac_client/examples/full-stack-with-auth/package.json +0 -28
  129. jac_client/examples/full-stack-with-auth/vite.config.js +0 -29
  130. jac_client/examples/little-x/app.jac +0 -724
  131. jac_client/examples/little-x/package.json +0 -23
  132. jac_client/examples/little-x/submit-button.jac +0 -8
  133. jac_client/examples/nested-folders/nested-advance/.babelrc +0 -9
  134. jac_client/examples/nested-folders/nested-advance/ButtonRoot.jac +0 -11
  135. jac_client/examples/nested-folders/nested-advance/README.md +0 -77
  136. jac_client/examples/nested-folders/nested-advance/app.jac +0 -35
  137. jac_client/examples/nested-folders/nested-advance/level1/ButtonSecondL.jac +0 -19
  138. jac_client/examples/nested-folders/nested-advance/level1/Card.jac +0 -43
  139. jac_client/examples/nested-folders/nested-advance/level1/level2/ButtonThirdL.jac +0 -25
  140. jac_client/examples/nested-folders/nested-advance/package.json +0 -29
  141. jac_client/examples/nested-folders/nested-advance/vite.config.js +0 -28
  142. jac_client/examples/nested-folders/nested-basic/.babelrc +0 -9
  143. jac_client/examples/nested-folders/nested-basic/README.md +0 -183
  144. jac_client/examples/nested-folders/nested-basic/app.jac +0 -13
  145. jac_client/examples/nested-folders/nested-basic/app.js +0 -7
  146. jac_client/examples/nested-folders/nested-basic/button.jac +0 -7
  147. jac_client/examples/nested-folders/nested-basic/components/button.jac +0 -7
  148. jac_client/examples/nested-folders/nested-basic/package.json +0 -28
  149. jac_client/examples/nested-folders/nested-basic/vite.config.js +0 -27
  150. jac_client/examples/with-router/.babelrc +0 -9
  151. jac_client/examples/with-router/README.md +0 -17
  152. jac_client/examples/with-router/app.jac +0 -323
  153. jac_client/examples/with-router/package.json +0 -28
  154. jac_client/examples/with-router/vite.config.js +0 -27
  155. jac_client/plugin/cli.py +0 -244
  156. jac_client/plugin/client.py +0 -152
  157. jac_client/plugin/client_runtime.jac +0 -234
  158. jac_client/plugin/vite_client_bundle.py +0 -503
  159. jac_client/tests/fixtures/basic-app/app.jac +0 -23
  160. jac_client/tests/fixtures/cl_file/app.cl.jac +0 -48
  161. jac_client/tests/fixtures/cl_file/app.jac +0 -15
  162. jac_client/tests/fixtures/client_app_with_antd/app.jac +0 -34
  163. jac_client/tests/fixtures/js_import/app.jac +0 -34
  164. jac_client/tests/fixtures/js_import/utils.js +0 -21
  165. jac_client/tests/fixtures/package-lock.json +0 -329
  166. jac_client/tests/fixtures/package.json +0 -11
  167. jac_client/tests/fixtures/relative_import/app.jac +0 -11
  168. jac_client/tests/fixtures/relative_import/button.jac +0 -7
  169. jac_client/tests/fixtures/spawn_test/app.jac +0 -129
  170. jac_client/tests/fixtures/test_fragments_spread/app.jac +0 -67
  171. jac_client/tests/test_asset_examples.py +0 -322
  172. jac_client/tests/test_cl.py +0 -530
  173. jac_client/tests/test_create_jac_app.py +0 -131
  174. jac_client/tests/test_nested_file.py +0 -374
  175. jac_client-0.2.2.dist-info/RECORD +0 -171
  176. jac_client-0.2.2.dist-info/entry_points.txt +0 -4
@@ -1,773 +0,0 @@
1
- # Lifecycle Hooks in Jac: Component Lifecycle Management
2
-
3
- Learn how to use React's `useEffect` and `useState` hooks to manage component state, initialization, side effects, and cleanup.
4
-
5
- ---
6
-
7
- ## Table of Contents
8
-
9
- - [What are Lifecycle Hooks?](#what-are-lifecycle-hooks)
10
- - [React Hooks (Recommended)](#react-hooks-recommended)
11
- - [useState](#usestate)
12
- - [useEffect](#useeffect)
13
- - [Common Use Cases](#common-use-cases)
14
- - [Complete Examples](#complete-examples)
15
- - [Best Practices](#best-practices)
16
- - [Legacy Jac Hooks](#legacy-jac-hooks)
17
-
18
- ---
19
-
20
- ## What are Lifecycle Hooks?
21
-
22
- Lifecycle hooks are functions that let you run code at specific points in a component's lifecycle:
23
- - **When component mounts**: Run initialization code once
24
- - **When component updates**: React to state changes
25
- - **When component unmounts**: Clean up resources
26
-
27
- **Jac uses React hooks as the standard approach:**
28
- - **useState**: Manage component state
29
- - **useEffect**: Handle side effects, lifecycle events, and cleanup
30
-
31
- **Key Benefits:**
32
- - **Initialization**: Load data when component appears
33
- - **Side Effects**: Set up subscriptions, timers, or listeners
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
37
-
38
- ---
39
-
40
- ## React Hooks (Recommended)
41
-
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
74
-
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
-
98
- cl {
99
- def MyComponent() -> any {
100
- let [data, setData] = useState(None);
101
-
102
- useEffect(lambda -> None {
103
- console.log("Component mounted!");
104
- # Load initial data
105
- async def loadData() -> None {
106
- result = await jacSpawn("get_data", "", {});
107
- setData(result);
108
- }
109
- loadData();
110
- }, []); # Empty array means run only on mount
111
-
112
- return <div>My Component</div>;
113
- }
114
- }
115
- ```
116
-
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
124
-
125
- #### useEffect with Dependencies
126
-
127
- ```jac
128
- cl import from react { useState, useEffect }
129
-
130
- cl {
131
- def Counter() -> any {
132
- let [count, setCount] = useState(0);
133
-
134
- useEffect(lambda -> None {
135
- console.log("Count changed to:", count);
136
- document.title = "Count: " + str(count);
137
- }, [count]); # Run when count changes
138
-
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
- ```
150
-
151
- #### useEffect with Cleanup
152
-
153
- ```jac
154
- cl import from react { useEffect }
155
-
156
- cl {
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
- ```
174
-
175
- ---
176
-
177
- ## Common Use Cases
178
-
179
- ### 1. Loading Initial Data
180
-
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 }
186
-
187
- cl {
188
- def TodoApp() -> any {
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
- }
202
- loadTodos();
203
- }, []); # Empty array = run only on mount
204
-
205
- if loading {
206
- return <div>Loading...</div>;
207
- }
208
-
209
- return <div>
210
- {todos.map(lambda todo: any -> any {
211
- return <TodoItem todo={todo} />;
212
- })}
213
- </div>;
214
- }
215
- }
216
- ```
217
-
218
- ### 2. Setting Up Event Listeners
219
-
220
- Set up event listeners with proper cleanup:
221
-
222
- ```jac
223
- cl import from react { useState, useEffect }
224
-
225
- cl {
226
- def WindowResizeHandler() -> any {
227
- let [width, setWidth] = useState(0);
228
- let [height, setHeight] = useState(0);
229
-
230
- useEffect(lambda -> any {
231
- def handleResize() -> None {
232
- setWidth(window.innerWidth);
233
- setHeight(window.innerHeight);
234
- }
235
-
236
- # Set initial size
237
- handleResize();
238
-
239
- # Add listener
240
- window.addEventListener("resize", handleResize);
241
-
242
- # Cleanup function
243
- return lambda -> None {
244
- window.removeEventListener("resize", handleResize);
245
- };
246
- }, []);
247
-
248
- return <div>
249
- Window size: {width} x {height}
250
- </div>;
251
- }
252
- }
253
- ```
254
-
255
- ### 3. Fetching User Data
256
-
257
- Load user-specific data when a component mounts:
258
-
259
- ```jac
260
- cl import from react { useState, useEffect }
261
- cl import from '@jac-client/utils' { jacSpawn }
262
-
263
- cl {
264
- def ProfileView() -> any {
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
- }
280
- loadUserProfile();
281
- }, []);
282
-
283
- if loading {
284
- return <div>Loading profile...</div>;
285
- }
286
-
287
- if not profile {
288
- return <div>No profile found</div>;
289
- }
290
-
291
- return <div>
292
- <h1>{profile.username}</h1>
293
- <p>{profile.email}</p>
294
- </div>;
295
- }
296
- }
297
- ```
298
-
299
- ### 4. Initializing Third-Party Libraries
300
-
301
- Initialize external libraries or APIs:
302
-
303
- ```jac
304
- cl import from react { useEffect }
305
-
306
- cl {
307
- def ChartComponent() -> any {
308
- useEffect(lambda -> any {
309
- # Initialize chart library
310
- chart = new Chart("myChart", {
311
- "type": "line",
312
- "data": chartData,
313
- "options": chartOptions
314
- });
315
-
316
- # Cleanup function
317
- return lambda -> None {
318
- chart.destroy();
319
- };
320
- }, []);
321
-
322
- return <canvas id="myChart"></canvas>;
323
- }
324
- }
325
- ```
326
-
327
- ### 5. Focusing Input Fields
328
-
329
- Focus an input field when a component mounts:
330
-
331
- ```jac
332
- cl import from react { useEffect }
333
-
334
- cl {
335
- def SearchBar() -> any {
336
- useEffect(lambda -> None {
337
- # Focus search input on mount
338
- inputEl = document.getElementById("search-input");
339
- if inputEl {
340
- inputEl.focus();
341
- }
342
- }, []);
343
-
344
- return <input
345
- id="search-input"
346
- type="text"
347
- placeholder="Search..."
348
- />;
349
- }
350
- }
351
- ```
352
-
353
- ---
354
-
355
- ## Complete Examples
356
-
357
- ### Example 1: Todo App with Data Loading
358
-
359
- ```jac
360
- cl import from react { useState, useEffect }
361
- cl import from '@jac-client/utils' { jacSpawn }
362
-
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);
374
- }
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("");
390
- }
391
-
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
- }));
407
- }
408
-
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;
417
- }
418
-
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>
466
- </div>;
467
- }
468
- }
469
- ```
470
-
471
- ### Example 2: Dashboard with Multiple Data Sources
472
-
473
- ```jac
474
- cl import from react { useState, useEffect }
475
- cl import from '@jac-client/utils' { jacSpawn }
476
-
477
- cl {
478
- def Dashboard() -> any {
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
- }
497
- loadDashboardData();
498
- }, []);
499
-
500
- if loading {
501
- return <div>Loading dashboard...</div>;
502
- }
503
-
504
- return <div>
505
- <StatsView stats={stats} />
506
- <ActivityList activities={activity} />
507
- </div>;
508
- }
509
- }
510
- ```
511
-
512
- ### Example 3: Timer Component with Cleanup
513
-
514
- Proper cleanup when component unmounts:
515
-
516
- ```jac
517
- cl import from react { useState, useEffect }
518
-
519
- cl {
520
- def TimerComponent() -> any {
521
- let [seconds, setSeconds] = useState(0);
522
-
523
- useEffect(lambda -> any {
524
- # Set up timer
525
- intervalId = setInterval(lambda -> None {
526
- setSeconds(lambda prev: int -> int { return prev + 1; });
527
- }, 1000);
528
-
529
- # Cleanup function - runs when component unmounts
530
- return lambda -> None {
531
- clearInterval(intervalId);
532
- };
533
- }, []);
534
-
535
- return <div>Timer: {seconds} seconds</div>;
536
- }
537
- }
538
- ```
539
-
540
- ---
541
-
542
- ## Best Practices
543
-
544
- ### 1. Always Specify Dependencies
545
-
546
- Be explicit about what your effect depends on:
547
-
548
- ```jac
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
- ```
564
-
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
- }, []);
583
- ```
584
-
585
- ### 3. Clean Up Side Effects
586
-
587
- Always clean up event listeners, timers, and subscriptions:
588
-
589
- ```jac
590
- # Good: Cleanup function removes event listener
591
- useEffect(lambda -> any {
592
- def handleResize() -> None {
593
- setWidth(window.innerWidth);
594
- }
595
-
596
- window.addEventListener("resize", handleResize);
597
-
598
- return lambda -> None {
599
- window.removeEventListener("resize", handleResize);
600
- };
601
- }, []);
602
- ```
603
-
604
- ### 4. Use Loading States
605
-
606
- Show loading indicators while data is being fetched:
607
-
608
- ```jac
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
- }
626
- }
627
- loadData();
628
- }, []);
629
-
630
- if loading { return <div>Loading...</div>; }
631
- if error { return <div>Error: {error}</div>; }
632
- return <div>{data}</div>;
633
- }
634
- ```
635
-
636
- ### 5. Keep Effects Focused
637
-
638
- Each effect should have a single responsibility:
639
-
640
- ```jac
641
- # Good: Separate effects for separate concerns
642
- def Component() -> any {
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
- }, []);
654
-
655
- return <div>Component</div>;
656
- }
657
- ```
658
-
659
- ### 6. Avoid Stale Closures
660
-
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
- }, []);
681
- ```
682
-
683
- ---
684
-
685
- ## Summary
686
-
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:
704
-
705
- ```jac
706
- # Legacy approach - use useEffect instead
707
- def Component() -> any {
708
- onMount(lambda -> None {
709
- loadData();
710
- });
711
- return <div>Component</div>;
712
- }
713
- ```
714
-
715
- **Modern equivalent:**
716
-
717
- ```jac
718
- # Modern approach with React hooks
719
- def Component() -> any {
720
- useEffect(lambda -> None {
721
- loadData();
722
- }, []);
723
- return <div>Component</div>;
724
- }
725
- ```
726
-
727
- ### `createState()` - Legacy
728
-
729
- The `createState()` hook was a Jac-specific state management solution:
730
-
731
- ```jac
732
- # Legacy approach - use useState instead
733
- let [state, setState] = createState({"count": 0});
734
-
735
- def Component() -> any {
736
- s = state();
737
- return <div>{s.count}</div>;
738
- }
739
- ```
740
-
741
- **Modern equivalent:**
742
-
743
- ```jac
744
- # Modern approach with React hooks
745
- def Component() -> any {
746
- let [count, setCount] = useState(0);
747
- return <div>{count}</div>;
748
- }
749
- ```
750
-
751
- ### `createSignal()` and `createEffect()` - Legacy
752
-
753
- These were Signal-based reactive primitives from Jac:
754
-
755
- ```jac
756
- # Legacy approach
757
- let [count, setCount] = createSignal(0);
758
-
759
- createEffect(lambda -> None {
760
- console.log("Count:", count());
761
- });
762
- ```
763
-
764
- **Modern equivalent:**
765
-
766
- ```jac
767
- # Modern approach with React hooks
768
- let [count, setCount] = useState(0);
769
-
770
- useEffect(lambda -> None {
771
- console.log("Count:", count);
772
- }, [count]);
773
- ```