jac-client 0.1.0__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 (33) hide show
  1. jac_client/docs/README.md +629 -0
  2. jac_client/docs/advanced-state.md +706 -0
  3. jac_client/docs/imports.md +650 -0
  4. jac_client/docs/lifecycle-hooks.md +554 -0
  5. jac_client/docs/routing.md +530 -0
  6. jac_client/examples/little-x/app.jac +615 -0
  7. jac_client/examples/little-x/package-lock.json +2840 -0
  8. jac_client/examples/little-x/package.json +23 -0
  9. jac_client/examples/little-x/submit-button.jac +8 -0
  10. jac_client/examples/todo-app/README.md +82 -0
  11. jac_client/examples/todo-app/app.jac +683 -0
  12. jac_client/examples/todo-app/package-lock.json +999 -0
  13. jac_client/examples/todo-app/package.json +22 -0
  14. jac_client/plugin/cli.py +328 -0
  15. jac_client/plugin/client.py +41 -0
  16. jac_client/plugin/client_runtime.jac +941 -0
  17. jac_client/plugin/vite_client_bundle.py +470 -0
  18. jac_client/tests/__init__.py +2 -0
  19. jac_client/tests/fixtures/button.jac +6 -0
  20. jac_client/tests/fixtures/client_app.jac +18 -0
  21. jac_client/tests/fixtures/client_app_with_antd.jac +21 -0
  22. jac_client/tests/fixtures/js_import.jac +30 -0
  23. jac_client/tests/fixtures/package-lock.json +329 -0
  24. jac_client/tests/fixtures/package.json +11 -0
  25. jac_client/tests/fixtures/relative_import.jac +13 -0
  26. jac_client/tests/fixtures/test_fragments_spread.jac +44 -0
  27. jac_client/tests/fixtures/utils.js +22 -0
  28. jac_client/tests/test_cl.py +360 -0
  29. jac_client/tests/test_create_jac_app.py +139 -0
  30. jac_client-0.1.0.dist-info/METADATA +126 -0
  31. jac_client-0.1.0.dist-info/RECORD +33 -0
  32. jac_client-0.1.0.dist-info/WHEEL +4 -0
  33. jac_client-0.1.0.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,554 @@
1
+ # Lifecycle Hooks in Jac: Component Lifecycle Management
2
+
3
+ Learn how to use `onMount()` and other lifecycle hooks to manage component initialization, side effects, and cleanup.
4
+
5
+ ---
6
+
7
+ ## 📚 Table of Contents
8
+
9
+ - [What are Lifecycle Hooks?](#what-are-lifecycle-hooks)
10
+ - [The `onMount()` Hook](#the-onmount-hook)
11
+ - [Common Use Cases](#common-use-cases)
12
+ - [Complete Examples](#complete-examples)
13
+ - [Best Practices](#best-practices)
14
+
15
+ ---
16
+
17
+ ## What are Lifecycle Hooks?
18
+
19
+ Lifecycle hooks are functions that let you run code at specific points in a component's lifecycle:
20
+ - **When component mounts**: Run initialization code once
21
+ - **When component updates**: React to state changes
22
+ - **When component unmounts**: Clean up resources
23
+
24
+ In Jac, the primary lifecycle hook is `onMount()`, which runs code once when a component first renders.
25
+
26
+ **Key Benefits:**
27
+ - **Initialization**: Load data when component appears
28
+ - **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
31
+
32
+ ---
33
+
34
+ ## The `onMount()` Hook
35
+
36
+ ### Basic Usage
37
+
38
+ ```jac
39
+ cl {
40
+ def MyComponent() -> any {
41
+ onMount(lambda -> None {
42
+ console.log("Component mounted!");
43
+ # Load initial data
44
+ loadData();
45
+ });
46
+
47
+ return <div>My Component</div>;
48
+ }
49
+ }
50
+ ```
51
+
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
57
+
58
+ ### Key Characteristics
59
+
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
64
+
65
+ ---
66
+
67
+ ## Common Use Cases
68
+
69
+ ### 1. Loading Initial Data
70
+
71
+ The most common use case is loading data when a component mounts:
72
+
73
+ ```jac
74
+ cl {
75
+ let [todoState, setTodoState] = createState({
76
+ "items": [],
77
+ "loading": True
78
+ });
79
+
80
+ async def loadTodos() -> None {
81
+ setTodoState({"loading": True});
82
+
83
+ # Fetch todos from backend
84
+ todos = await __jacSpawn("read_todos");
85
+
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
+ }
94
+
95
+ setTodoState({"items": items, "loading": False});
96
+ }
97
+
98
+ def TodoApp() -> any {
99
+ # Load todos when component mounts
100
+ onMount(lambda -> None {
101
+ loadTodos();
102
+ });
103
+
104
+ s = todoState();
105
+
106
+ if s.loading {
107
+ return <div>Loading...</div>;
108
+ }
109
+
110
+ return <div>
111
+ {[TodoItem(item) for item in s.items]}
112
+ </div>;
113
+ }
114
+ }
115
+ ```
116
+
117
+ ### 2. Setting Up Event Listeners
118
+
119
+ Set up event listeners that need to persist across re-renders:
120
+
121
+ ```jac
122
+ cl {
123
+ 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
+ }
135
+
136
+ def ResizeDisplay() -> any {
137
+ onMount(lambda -> None {
138
+ # Set initial size
139
+ handleResize();
140
+
141
+ # Add listener
142
+ window.addEventListener("resize", handleResize);
143
+ });
144
+
145
+ s = windowSize();
146
+ return <div>
147
+ Window size: {s.width} x {s.height}
148
+ </div>;
149
+ }
150
+
151
+ return ResizeDisplay();
152
+ }
153
+ }
154
+ ```
155
+
156
+ ### 3. Fetching User Data
157
+
158
+ Load user-specific data when a component mounts:
159
+
160
+ ```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
+ }
181
+
182
+ def ProfileView() -> any {
183
+ onMount(lambda -> None {
184
+ loadUserProfile();
185
+ });
186
+
187
+ s = userState();
188
+
189
+ if s.loading {
190
+ return <div>Loading profile...</div>;
191
+ }
192
+
193
+ if not s.profile {
194
+ return <div>No profile found</div>;
195
+ }
196
+
197
+ return <div>
198
+ <h1>{s.profile.username}</h1>
199
+ <p>{s.profile.email}</p>
200
+ </div>;
201
+ }
202
+ }
203
+ ```
204
+
205
+ ### 4. Initializing Third-Party Libraries
206
+
207
+ Initialize external libraries or APIs:
208
+
209
+ ```jac
210
+ cl {
211
+ def ChartComponent() -> any {
212
+ onMount(lambda -> None {
213
+ # Initialize chart library
214
+ chart = new Chart("myChart", {
215
+ "type": "line",
216
+ "data": chartData,
217
+ "options": chartOptions
218
+ });
219
+ });
220
+
221
+ return <canvas id="myChart"></canvas>;
222
+ }
223
+ }
224
+ ```
225
+
226
+ ### 5. Focusing Input Fields
227
+
228
+ Focus an input field when a component mounts:
229
+
230
+ ```jac
231
+ cl {
232
+ def SearchBar() -> any {
233
+ onMount(lambda -> None {
234
+ # Focus search input on mount
235
+ inputEl = document.getElementById("search-input");
236
+ if inputEl {
237
+ inputEl.focus();
238
+ }
239
+ });
240
+
241
+ return <input
242
+ id="search-input"
243
+ type="text"
244
+ placeholder="Search..."
245
+ />;
246
+ }
247
+ }
248
+ ```
249
+
250
+ ---
251
+
252
+ ## Complete Examples
253
+
254
+ ### Example 1: Todo App with Data Loading
255
+
256
+ ```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});
266
+
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
+ });
277
+ }
278
+
279
+ setTodoState({"items": items, "loading": False});
280
+ } except Exception as err {
281
+ console.error("Failed to load todos:", err);
282
+ setTodoState({"loading": False});
283
+ }
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
+
294
+ if s.loading {
295
+ return <div style={{
296
+ "textAlign": "center",
297
+ "padding": "40px"
298
+ }}>
299
+ Loading todos...
300
+ </div>;
301
+ }
302
+
303
+ itemsArr = filteredItems();
304
+ children = [];
305
+ for it in itemsArr {
306
+ children.push(TodoItem(it));
307
+ }
308
+
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>
316
+ </div>;
317
+ }
318
+ }
319
+ ```
320
+
321
+ ### Example 2: Dashboard with Multiple Data Sources
322
+
323
+ ```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
+ }
346
+
347
+ def Dashboard() -> any {
348
+ # Load all dashboard data on mount
349
+ onMount(lambda -> None {
350
+ loadDashboardData();
351
+ });
352
+
353
+ s = dashboardState();
354
+
355
+ if s.loading {
356
+ return <div>Loading dashboard...</div>;
357
+ }
358
+
359
+ return <div>
360
+ <StatsView stats={s.stats} />
361
+ <ActivityList activities={s.recentActivity} />
362
+ </div>;
363
+ }
364
+ }
365
+ ```
366
+
367
+ ### Example 3: Component with Cleanup (Future)
368
+
369
+ While `onMount()` doesn't currently support cleanup directly, you can store cleanup functions:
370
+
371
+ ```jac
372
+ cl {
373
+ let cleanupFunctions = [];
374
+
375
+ def TimerComponent() -> any {
376
+ onMount(lambda -> None {
377
+ # Set up timer
378
+ intervalId = setInterval(lambda -> None {
379
+ console.log("Timer tick");
380
+ }, 1000);
381
+
382
+ # Store cleanup function
383
+ cleanupFunctions.push(lambda -> None {
384
+ clearInterval(intervalId);
385
+ });
386
+ });
387
+
388
+ return <div>Timer Component</div>;
389
+ }
390
+ }
391
+ ```
392
+
393
+ ---
394
+
395
+ ## Best Practices
396
+
397
+ ### 1. Use `onMount()` for Initialization Only
398
+
399
+ ```jac
400
+ # ✅ Good: One-time initialization
401
+ def Component() -> any {
402
+ onMount(lambda -> None {
403
+ loadInitialData();
404
+ });
405
+ return <div>Component</div>;
406
+ }
407
+
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
+ }
417
+ ```
418
+
419
+ ### 2. Handle Async Operations
420
+
421
+ Always handle async operations properly:
422
+
423
+ ```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);
431
+ }
432
+ }
433
+
434
+ def Component() -> any {
435
+ onMount(lambda -> None {
436
+ loadData();
437
+ });
438
+ return <div>Component</div>;
439
+ }
440
+ ```
441
+
442
+ ### 3. Check Conditions Before Operations
443
+
444
+ Verify prerequisites before executing operations:
445
+
446
+ ```jac
447
+ # ✅ Good: Check authentication first
448
+ def ProtectedComponent() -> any {
449
+ onMount(lambda -> None {
450
+ if not jacIsLoggedIn() {
451
+ navigate("/login");
452
+ return;
453
+ }
454
+ loadUserData();
455
+ });
456
+ return <div>Protected Content</div>;
457
+ }
458
+ ```
459
+
460
+ ### 4. Use Loading States
461
+
462
+ Show loading indicators while data is being fetched:
463
+
464
+ ```jac
465
+ def Component() -> any {
466
+ let [dataState, setDataState] = createState({
467
+ "data": None,
468
+ "loading": True
469
+ });
470
+
471
+ onMount(lambda -> None {
472
+ loadData();
473
+ });
474
+
475
+ s = dataState();
476
+ if s.loading {
477
+ return <div>Loading...</div>;
478
+ }
479
+
480
+ return <div>{s.data}</div>;
481
+ }
482
+ ```
483
+
484
+ ### 5. Avoid Multiple `onMount()` Calls
485
+
486
+ Group initialization logic in a single `onMount()` call:
487
+
488
+ ```jac
489
+ # ✅ Good: Single onMount with multiple operations
490
+ def Component() -> any {
491
+ onMount(lambda -> None {
492
+ loadData();
493
+ setupEventListeners();
494
+ initializeThirdParty();
495
+ });
496
+ return <div>Component</div>;
497
+ }
498
+
499
+ # ❌ Avoid: Multiple onMount calls
500
+ def Component() -> any {
501
+ onMount(lambda -> None { loadData(); });
502
+ onMount(lambda -> None { setupEventListeners(); });
503
+ onMount(lambda -> None { initializeThirdParty(); });
504
+ return <div>Component</div>;
505
+ }
506
+ ```
507
+
508
+ ---
509
+
510
+ ## When NOT to Use `onMount()`
511
+
512
+ ### Use `createEffect()` for Reactive Side Effects
513
+
514
+ If you need code to run when state changes, use `createEffect()` instead:
515
+
516
+ ```jac
517
+ # ✅ Use createEffect() for state-dependent side effects
518
+ def Component() -> any {
519
+ let [count, setCount] = createSignal(0);
520
+
521
+ createEffect(lambda -> None {
522
+ console.log("Count changed:", count());
523
+ });
524
+
525
+ return <div>
526
+ <button onClick={lambda -> None { setCount(count() + 1); }}>
527
+ Count: {count()}
528
+ </button>
529
+ </div>;
530
+ }
531
+
532
+ # ❌ onMount() won't run when state changes
533
+ def Component() -> any {
534
+ let [count, setCount] = createSignal(0);
535
+
536
+ onMount(lambda -> None {
537
+ console.log("Count:", count()); # Only logs initial value
538
+ });
539
+
540
+ return <div>Component</div>;
541
+ }
542
+ ```
543
+
544
+ ---
545
+
546
+ ## Summary
547
+
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)
552
+
553
+ Lifecycle hooks help you manage component initialization and side effects effectively! 🎯
554
+