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
@@ -0,0 +1,530 @@
1
+ # Step 5: Local State
2
+
3
+ > **💡 Quick Tip:** Each step has two parts. **Part 1** shows you what to build. **Part 2** explains why it works. Want to just build? Skip all Part 2 sections!
4
+
5
+ In this step, you'll learn about **state** - the data that makes your app interactive and dynamic!
6
+
7
+ ---
8
+
9
+ ## 🏗️ Part 1: Building the App
10
+
11
+ ### Step 5.1: First, Let's See Why Normal Variables Don't Work
12
+
13
+ Let's try using a normal variable to track todos:
14
+
15
+ ```jac
16
+ cl {
17
+ # ... (keep all your components from Step 4)
18
+
19
+ def app() -> any {
20
+ # Try using a normal variable
21
+ let todos = [
22
+ {"text": "Learn Jac", "done": false},
23
+ {"text": "Build app", "done": false}
24
+ ];
25
+
26
+ return <div style={{
27
+ "maxWidth": "600px",
28
+ "margin": "20px auto",
29
+ "padding": "20px"
30
+ }}>
31
+ <h1>My Todos ({todos.length})</h1>
32
+ <p>Todos: {todos.length}</p>
33
+ </div>;
34
+ }
35
+ }
36
+ ```
37
+
38
+ This works for displaying data, but **what if we want to change it?** Normal variables can't trigger UI updates!
39
+
40
+ ### Step 5.2: Introducing `useState`
41
+
42
+ To make data interactive, we need `useState`. First, import it:
43
+
44
+ ```jac
45
+ cl import from react {useState}
46
+
47
+ cl {
48
+ def app() -> any {
49
+ # Create state with useState
50
+ let [todos, setTodos] = useState([]);
51
+
52
+ return <div style={{"padding": "20px"}}>
53
+ <h1>My Todos</h1>
54
+ <p>Total: {todos.length}</p>
55
+ </div>;
56
+ }
57
+ }
58
+ ```
59
+
60
+ **What's happening:**
61
+ - `useState([])` creates state with initial value `[]` (empty array)
62
+ - Returns two things:
63
+ - `todos` - The current value (read-only)
64
+ - `setTodos` - Function to update the value
65
+
66
+ ### Step 5.3: Add State for Input Field
67
+
68
+ Let's make the input field work:
69
+
70
+ ```jac
71
+ cl import from react {useState}
72
+
73
+ cl {
74
+ def TodoInput(props: any) -> any {
75
+ return <div style={{
76
+ "display": "flex",
77
+ "gap": "8px",
78
+ "marginBottom": "16px"
79
+ }}>
80
+ <input
81
+ type="text"
82
+ value={props.input}
83
+ placeholder="What needs to be done?"
84
+ style={{
85
+ "flex": "1",
86
+ "padding": "8px",
87
+ "border": "1px solid #ddd",
88
+ "borderRadius": "4px"
89
+ }}
90
+ />
91
+ <button style={{
92
+ "padding": "8px 16px",
93
+ "background": "#3b82f6",
94
+ "color": "#ffffff",
95
+ "border": "none",
96
+ "borderRadius": "4px",
97
+ "cursor": "pointer"
98
+ }}>
99
+ Add
100
+ </button>
101
+ </div>;
102
+ }
103
+
104
+ def app() -> any {
105
+ # State for input field
106
+ let [input, setInput] = useState("");
107
+
108
+ return <div style={{
109
+ "maxWidth": "600px",
110
+ "margin": "20px auto",
111
+ "padding": "20px"
112
+ }}>
113
+ <h1>My Todos</h1>
114
+ <TodoInput input={input} />
115
+ <p>You typed: {input}</p>
116
+ </div>;
117
+ }
118
+ }
119
+ ```
120
+
121
+ **Try typing in the input!** Nothing happens yet because we haven't connected the onChange event (we'll do that in the next step).
122
+
123
+ ### Step 5.4: Add State for Todos List
124
+
125
+ Now let's track our todos list with state:
126
+
127
+ ```jac
128
+ cl import from react {useState}
129
+
130
+ cl {
131
+ def TodoItem(props: any) -> any {
132
+ return <div style={{
133
+ "display": "flex",
134
+ "alignItems": "center",
135
+ "gap": "10px",
136
+ "padding": "10px",
137
+ "borderBottom": "1px solid #e5e7eb"
138
+ }}>
139
+ <input type="checkbox" checked={props.done} />
140
+ <span style={{
141
+ "flex": "1",
142
+ "textDecoration": ("line-through" if props.done else "none"),
143
+ "color": ("#999" if props.done else "#000")
144
+ }}>
145
+ {props.text}
146
+ </span>
147
+ <button style={{
148
+ "padding": "4px 8px",
149
+ "background": "#ef4444",
150
+ "color": "white",
151
+ "border": "none",
152
+ "borderRadius": "4px",
153
+ "cursor": "pointer"
154
+ }}>
155
+ Delete
156
+ </button>
157
+ </div>;
158
+ }
159
+
160
+ def app() -> any {
161
+ # State for todos
162
+ let [todos, setTodos] = useState([
163
+ {"text": "Learn Jac basics", "done": false},
164
+ {"text": "Build a todo app", "done": false}
165
+ ]);
166
+
167
+ return <div style={{
168
+ "maxWidth": "600px",
169
+ "margin": "20px auto",
170
+ "padding": "20px",
171
+ "background": "#ffffff",
172
+ "borderRadius": "8px"
173
+ }}>
174
+ <h1>My Todos</h1>
175
+
176
+ # Display todos
177
+ <div>
178
+ {todos.map(lambda todo: any -> any {
179
+ return <TodoItem
180
+ text={todo.text}
181
+ done={todo.done}
182
+ />;
183
+ })}
184
+ </div>
185
+
186
+ # Stats
187
+ <div style={{"marginTop": "16px", "color": "#666"}}>
188
+ {todos.length} items total
189
+ </div>
190
+ </div>;
191
+ }
192
+ }
193
+ ```
194
+
195
+ ### Step 5.5: Add State for Filter
196
+
197
+ Let's add filter state:
198
+
199
+ ```jac
200
+ cl import from react {useState}
201
+
202
+ cl {
203
+ # ... (keep all previous components)
204
+
205
+ def TodoFilters(props: any) -> any {
206
+ return <div style={{
207
+ "display": "flex",
208
+ "gap": "8px",
209
+ "marginBottom": "16px"
210
+ }}>
211
+ <button style={{
212
+ "padding": "6px 12px",
213
+ "background": ("#3b82f6" if props.filter == "all" else "#e5e7eb"),
214
+ "color": ("#ffffff" if props.filter == "all" else "#000000"),
215
+ "border": "none",
216
+ "borderRadius": "4px",
217
+ "cursor": "pointer"
218
+ }}>
219
+ All
220
+ </button>
221
+ <button style={{
222
+ "padding": "6px 12px",
223
+ "background": ("#3b82f6" if props.filter == "active" else "#e5e7eb"),
224
+ "color": ("#ffffff" if props.filter == "active" else "#000000"),
225
+ "border": "none",
226
+ "borderRadius": "4px",
227
+ "cursor": "pointer"
228
+ }}>
229
+ Active
230
+ </button>
231
+ <button style={{
232
+ "padding": "6px 12px",
233
+ "background": ("#3b82f6" if props.filter == "completed" else "#e5e7eb"),
234
+ "color": ("#ffffff" if props.filter == "completed" else "#000000"),
235
+ "border": "none",
236
+ "borderRadius": "4px",
237
+ "cursor": "pointer"
238
+ }}>
239
+ Completed
240
+ </button>
241
+ </div>;
242
+ }
243
+
244
+ def app() -> any {
245
+ let [todos, setTodos] = useState([
246
+ {"text": "Learn Jac basics", "done": false},
247
+ {"text": "Build a todo app", "done": true}
248
+ ]);
249
+ let [filter, setFilter] = useState("all");
250
+
251
+ return <div style={{
252
+ "maxWidth": "600px",
253
+ "margin": "20px auto",
254
+ "padding": "20px"
255
+ }}>
256
+ <h1>My Todos</h1>
257
+ <TodoFilters filter={filter} />
258
+
259
+ # Show current filter
260
+ <p>Current filter: {filter}</p>
261
+ </div>;
262
+ }
263
+ }
264
+ ```
265
+
266
+ **Notice:** The filter buttons now highlight based on the current filter! But clicking them doesn't work yet (we'll add that in Step 6).
267
+
268
+ ---
269
+
270
+ **⏭️ Want to skip the theory?** Jump to [Step 6: Event Handlers](./step-06-events.md)
271
+
272
+ ---
273
+
274
+ ## 💡 Part 2: Understanding the Concepts
275
+
276
+ ### What is State?
277
+
278
+ **State** is data that can change over time and causes your UI to update when it changes.
279
+
280
+ **Python analogy:**
281
+
282
+ ```python
283
+ # Python class with state
284
+ class TodoApp:
285
+ def __init__(self):
286
+ self.todos = [] # This is state
287
+
288
+ def add_todo(self, text):
289
+ self.todos.append(text) # Changing state
290
+ self.render() # Manually update UI
291
+ ```
292
+
293
+ ```jac
294
+ # Jac with React
295
+ def app() -> any {
296
+ let [todos, setTodos] = useState([]); # This is state
297
+
298
+ # When you call setTodos(), React automatically updates the UI!
299
+ }
300
+ ```
301
+
302
+ ### The `useState` Hook
303
+
304
+ ```jac
305
+ let [value, setValue] = useState(initialValue);
306
+ ```
307
+
308
+ **Returns a pair:**
309
+ 1. `value` - Current state value (read-only, don't modify directly!)
310
+ 2. `setValue` - Function to update state
311
+
312
+ **Examples:**
313
+
314
+ ```jac
315
+ # String state
316
+ let [name, setName] = useState("Alice");
317
+
318
+ # Number state
319
+ let [count, setCount] = useState(0);
320
+
321
+ # Boolean state
322
+ let [isOpen, setIsOpen] = useState(false);
323
+
324
+ # Array state
325
+ let [todos, setTodos] = useState([]);
326
+
327
+ # Object state
328
+ let [user, setUser] = useState({"name": "Alice", "age": 30});
329
+ ```
330
+
331
+ ### Why Use `useState`?
332
+
333
+ **Without useState (doesn't work):**
334
+
335
+ ```jac
336
+ def app() -> any {
337
+ let count = 0; # Normal variable
338
+
339
+ # Button click would change count, but UI won't update!
340
+ return <button>Count: {count}</button>;
341
+ }
342
+ ```
343
+
344
+ **With useState (works!):**
345
+
346
+ ```jac
347
+ def app() -> any {
348
+ let [count, setCount] = useState(0); # State
349
+
350
+ # When setCount is called, React re-renders the component!
351
+ return <button>Count: {count}</button>;
352
+ }
353
+ ```
354
+
355
+ ### Multiple State Variables
356
+
357
+ You can have multiple pieces of state:
358
+
359
+ ```jac
360
+ def app() -> any {
361
+ let [todos, setTodos] = useState([]);
362
+ let [input, setInput] = useState("");
363
+ let [filter, setFilter] = useState("all");
364
+ let [loading, setLoading] = useState(false);
365
+
366
+ # Use them independently
367
+ }
368
+ ```
369
+
370
+ Each state variable is independent and has its own update function.
371
+
372
+ ### State Naming Convention
373
+
374
+ Follow this pattern:
375
+
376
+ ```jac
377
+ # Pattern: [thing, setThing]
378
+ let [count, setCount] = useState(0);
379
+ let [name, setName] = useState("");
380
+ let [isOpen, setIsOpen] = useState(false);
381
+ let [todos, setTodos] = useState([]);
382
+
383
+ # ❌ Bad names
384
+ let [count, updateCount] = useState(0); # Inconsistent
385
+ let [x, y] = useState(0); # Not descriptive
386
+ ```
387
+
388
+ ### The `.map()` Method for Lists
389
+
390
+ To render a list of items, use `.map()`:
391
+
392
+ ```jac
393
+ {todos.map(lambda todo: any -> any {
394
+ return <TodoItem text={todo.text} done={todo.done} />;
395
+ })}
396
+ ```
397
+
398
+ **How it works:**
399
+
400
+ ```python
401
+ # Python equivalent
402
+ todos = [{"text": "Task 1"}, {"text": "Task 2"}]
403
+ items = [TodoItem(text=todo["text"]) for todo in todos]
404
+ ```
405
+
406
+ **Breakdown:**
407
+ - `todos.map(...)` - Loop through each todo
408
+ - `lambda todo: any -> any { ... }` - Function that runs for each item
409
+ - `return <TodoItem ... />` - Returns a component for each item
410
+
411
+ ### State is Immutable
412
+
413
+ **Never modify state directly:**
414
+
415
+ ```jac
416
+ # ❌ WRONG - Never do this!
417
+ let [todos, setTodos] = useState([]);
418
+ todos.push(newTodo); # DON'T modify directly!
419
+
420
+ # ✅ CORRECT - Create new array
421
+ let [todos, setTodos] = useState([]);
422
+ setTodos(todos.concat([newTodo])); # Create new array
423
+ ```
424
+
425
+ Why? Because React needs to detect changes to update the UI. If you modify directly, React won't know it changed!
426
+
427
+ ### Passing State to Children
428
+
429
+ State flows down through props:
430
+
431
+ ```jac
432
+ def Parent() -> any {
433
+ let [name, setName] = useState("Alice");
434
+
435
+ # Pass state down as props
436
+ return <Child name={name} />;
437
+ }
438
+
439
+ def Child(props: any) -> any {
440
+ # Access state via props
441
+ return <div>Hello, {props.name}!</div>;
442
+ }
443
+ ```
444
+
445
+ The child receives state but **cannot modify** the parent's state directly (we'll learn how to do that with callbacks in the next step).
446
+
447
+ ---
448
+
449
+ ## ✅ What You've Learned
450
+
451
+ - ✅ What state is and why we need it
452
+ - ✅ How to use the `useState` hook
453
+ - ✅ Creating multiple state variables
454
+ - ✅ State naming conventions
455
+ - ✅ Using `.map()` to render lists
456
+ - ✅ State is immutable (don't modify directly)
457
+ - ✅ Passing state to child components via props
458
+
459
+ ---
460
+
461
+ ## 🐛 Common Issues
462
+
463
+ ### Issue: UI not updating when state changes
464
+
465
+ **Check:** Are you modifying state directly?
466
+
467
+ ```jac
468
+ # ❌ Wrong
469
+ todos.push(newTodo);
470
+
471
+ # ✅ Correct
472
+ setTodos(todos.concat([newTodo]));
473
+ ```
474
+
475
+ ### Issue: "todos is not iterable"
476
+
477
+ **Check:** Did you initialize state as an array?
478
+
479
+ ```jac
480
+ # ❌ Wrong
481
+ let [todos, setTodos] = useState(); # undefined
482
+
483
+ # ✅ Correct
484
+ let [todos, setTodos] = useState([]); # empty array
485
+ ```
486
+
487
+ ### Issue: useState is not defined
488
+
489
+ **Check:** Did you import it?
490
+
491
+ ```jac
492
+ cl import from react {useState}
493
+ ```
494
+
495
+ ---
496
+
497
+ ## 🎯 Quick Exercise
498
+
499
+ Try adding more initial todos:
500
+
501
+ ```jac
502
+ let [todos, setTodos] = useState([
503
+ {"text": "Learn Jac basics", "done": true},
504
+ {"text": "Build a todo app", "done": false},
505
+ {"text": "Deploy to production", "done": false},
506
+ {"text": "Celebrate!", "done": false}
507
+ ]);
508
+ ```
509
+
510
+ And display the count of completed todos:
511
+
512
+ ```jac
513
+ let completedCount = todos.filter(lambda todo: any -> bool {
514
+ return todo.done;
515
+ }).length;
516
+
517
+ return <div>
518
+ <p>{completedCount} completed out of {todos.length}</p>
519
+ </div>;
520
+ ```
521
+
522
+ ---
523
+
524
+ ## ➡️ Next Step
525
+
526
+ Great! You now have state in your app, but you can't change it yet. Clicking buttons does nothing!
527
+
528
+ In the next step, we'll add **event handlers** to make your app fully interactive!
529
+
530
+ 👉 **[Continue to Step 6: Event Handlers](./step-06-events.md)**