jac-client 0.2.3__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.3.dist-info → jac_client-0.2.4.dist-info}/METADATA +28 -30
  6. jac_client-0.2.4.dist-info/RECORD +10 -0
  7. {jac_client-0.2.3.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.3.dist-info/RECORD +0 -171
  176. jac_client-0.2.3.dist-info/entry_points.txt +0 -4
@@ -1,1265 +0,0 @@
1
- # Advanced State Management in Jac
2
-
3
- Learn how to manage complex state in Jac using React hooks, combining multiple state instances, and building scalable state architectures.
4
-
5
- ---
6
-
7
- ## Table of Contents
8
-
9
- - [React Hooks Overview](#react-hooks-overview)
10
- - [Multiple State Variables](#multiple-state-variables)
11
- - [State Composition Patterns](#state-composition-patterns)
12
- - [Derived State](#derived-state)
13
- - [Advanced React Hooks](#advanced-react-hooks)
14
- - [State Management Patterns](#state-management-patterns)
15
- - [Best Practices](#best-practices)
16
-
17
- ---
18
-
19
- ## React Hooks Overview
20
-
21
- Jac uses React hooks for all state management. The most common hooks are:
22
-
23
- - **`useState`**: Manage component state
24
- - **`useEffect`**: Handle side effects and lifecycle
25
- - **`useReducer`**: Manage complex state logic
26
- - **`useContext`**: Share state across components
27
- - **`useMemo`**: Memoize expensive computations
28
- - **`useCallback`**: Memoize callback functions
29
-
30
- ### Basic useState Example
31
-
32
- ```jac
33
- cl import from react { useState, useEffect }
34
-
35
- cl {
36
- def TodoApp() -> any {
37
- let [todos, setTodos] = useState([]);
38
- let [filter, setFilter] = useState("all");
39
- let [loading, setLoading] = useState(False);
40
-
41
- useEffect(lambda -> None {
42
- async def loadTodos() -> None {
43
- setLoading(True);
44
- result = root spawn read_todos();
45
- setTodos(result.reports);
46
- setLoading(False);
47
- }
48
- loadTodos();
49
- }, []);
50
-
51
- return <div>{/* your UI */}</div>;
52
- }
53
-
54
- def app() -> any {
55
- return <TodoApp />;
56
- }
57
- }
58
- ```
59
-
60
- ---
61
-
62
- ## Multiple State Variables
63
-
64
- ### Separating State by Concern
65
-
66
- Instead of putting everything in one state object, split state into multiple variables:
67
-
68
- ```jac
69
- cl import from react { useState, useEffect }
70
-
71
- cl {
72
- def TodoApp() -> any {
73
- # Separate state variables for different concerns
74
- let [todos, setTodos] = useState([]);
75
- let [filter, setFilter] = useState("all");
76
- let [loading, setLoading] = useState(False);
77
- let [error, setError] = useState(None);
78
-
79
- useEffect(lambda -> None {
80
- async def loadTodos() -> None {
81
- setLoading(True);
82
- setError(None);
83
- try {
84
- result = root spawn read_todos();
85
- setTodos(result.reports);
86
- } catch (err) {
87
- setError(err);
88
- } finally {
89
- setLoading(False);
90
- }
91
- }
92
- loadTodos();
93
- }, []);
94
-
95
- if loading { return <div>Loading...</div>; }
96
- if error { return <div>Error: {error}</div>; }
97
-
98
- return <div>
99
- {todos.map(lambda todo: any -> any {
100
- return <TodoItem key={todo._jac_id} todo={todo} />;
101
- })}
102
- </div>;
103
- }
104
-
105
- def app() -> any {
106
- return <TodoApp />;
107
- }
108
- }
109
- ```
110
-
111
- **Benefits:**
112
- - **Separation of Concerns**: Each state variable manages one aspect
113
- - **Selective Updates**: Only components using specific state re-render
114
- - **Type Safety**: Each variable has its own type
115
- - **Clearer Code**: Easy to understand what each state represents
116
-
117
- ### When to Use Object State
118
-
119
- Sometimes an object makes sense for closely related data:
120
-
121
- ```jac
122
- cl import from react { useState }
123
-
124
- cl {
125
- def UserProfile() -> any {
126
- # Good: Related data in one object
127
- let [user, setUser] = useState({
128
- "name": "",
129
- "email": "",
130
- "avatar": ""
131
- });
132
-
133
- def updateName(name: str) -> None {
134
- setUser({...user, "name": name});
135
- }
136
-
137
- return <div>
138
- <input value={user.name} onChange={lambda e: any -> None {
139
- updateName(e.target.value);
140
- }} />
141
- </div>;
142
- }
143
- }
144
- ```
145
-
146
- ---
147
-
148
- ## State Composition Patterns
149
-
150
- ### Pattern 1: Feature-Based State
151
-
152
- Organize state by feature or domain using multiple `useState` calls:
153
-
154
- ```jac
155
- cl import from react { useState, useEffect }
156
-
157
- cl {
158
- def App() -> any {
159
- # User state
160
- let [user, setUser] = useState(None);
161
- let [isLoggedIn, setIsLoggedIn] = useState(False);
162
-
163
- # Todo state
164
- let [todos, setTodos] = useState([]);
165
- let [selectedId, setSelectedId] = useState(None);
166
-
167
- # UI state
168
- let [theme, setTheme] = useState("light");
169
- let [sidebarOpen, setSidebarOpen] = useState(False);
170
- let [modalOpen, setModalOpen] = useState(False);
171
-
172
- # Settings state
173
- let [notifications, setNotifications] = useState(True);
174
- let [language, setLanguage] = useState("en");
175
-
176
- useEffect(lambda -> None {
177
- async def loadData() -> None {
178
- result = root spawn get_user_data();
179
- setUser(result.user);
180
- setIsLoggedIn(True);
181
- }
182
- loadData();
183
- }, []);
184
-
185
- return <div className={theme}>
186
- {sidebarOpen and <Sidebar />}
187
- {todos.length > 0 and <TodoList items={todos} />}
188
- </div>;
189
- }
190
-
191
- def app() -> any {
192
- return <App />;
193
- }
194
- }
195
- ```
196
-
197
- ### Pattern 2: Local vs Global State
198
-
199
- Use Context for global state and `useState` for local state:
200
-
201
- ```jac
202
- cl import from react { useState, useContext, createContext }
203
-
204
- cl {
205
- # Create context for global state
206
- AppContext = createContext(None);
207
-
208
- def App() -> any {
209
- # Global state
210
- let [currentUser, setCurrentUser] = useState(None);
211
- let [theme, setTheme] = useState("light");
212
-
213
- appValue = {
214
- "currentUser": currentUser,
215
- "theme": theme,
216
- "setCurrentUser": setCurrentUser,
217
- "setTheme": setTheme
218
- };
219
-
220
- return <AppContext.Provider value={appValue}>
221
- <TodoForm />
222
- <TodoList />
223
- </AppContext.Provider>;
224
- }
225
-
226
- # Component with local state
227
- def TodoForm() -> any {
228
- # Access global context
229
- app = useContext(AppContext);
230
-
231
- # Local component state
232
- let [text, setText] = useState("");
233
- let [valid, setValid] = useState(False);
234
-
235
- def validate(value: str) -> None {
236
- setValid(len(value.trim()) > 0);
237
- }
238
-
239
- return <form>
240
- <input
241
- value={text}
242
- onChange={lambda e: any -> None {
243
- newText = e.target.value;
244
- setText(newText);
245
- validate(newText);
246
- }}
247
- style={{"background": ("#333" if app.theme == "dark" else "#fff")}}
248
- />
249
- </form>;
250
- }
251
-
252
- def TodoList() -> any {
253
- # Local list state
254
- let [sortBy, setSortBy] = useState("date");
255
- let [order, setOrder] = useState("asc");
256
-
257
- # Access global context
258
- app = useContext(AppContext);
259
-
260
- return <div>
261
- <h2>Welcome, {app.currentUser.name if app.currentUser else "Guest"}</h2>
262
- </div>;
263
- }
264
-
265
- def app() -> any {
266
- return <App />;
267
- }
268
- }
269
- ```
270
-
271
- ### Pattern 3: Custom Hooks (State Modules)
272
-
273
- Create reusable custom hooks for shared logic:
274
-
275
- ```jac
276
- cl import from react { useState, useEffect }
277
- cl import from '@jac-client/utils' { jacLogout }
278
-
279
- cl {
280
- # Custom hook: User management
281
- def useUser() -> dict {
282
- let [user, setUser] = useState(None);
283
- let [loading, setLoading] = useState(False);
284
- let [error, setError] = useState(None);
285
-
286
- async def loadUser() -> None {
287
- setLoading(True);
288
- setError(None);
289
- try {
290
- result = root spawn get_current_user();
291
- setUser(result);
292
- } catch (err) {
293
- setError(err);
294
- } finally {
295
- setLoading(False);
296
- }
297
- }
298
-
299
- def logout() -> None {
300
- jacLogout();
301
- setUser(None);
302
- }
303
-
304
- useEffect(lambda -> None {
305
- loadUser();
306
- }, []);
307
-
308
- return {
309
- "user": user,
310
- "loading": loading,
311
- "error": error,
312
- "logout": logout,
313
- "reload": loadUser
314
- };
315
- }
316
-
317
- # Custom hook: Todo management
318
- def useTodos() -> dict {
319
- let [todos, setTodos] = useState([]);
320
- let [loading, setLoading] = useState(False);
321
-
322
- async def loadTodos() -> None {
323
- setLoading(True);
324
- try {
325
- result = root spawn read_todos();
326
- setTodos(result.reports);
327
- } finally {
328
- setLoading(False);
329
- }
330
- }
331
-
332
- async def addTodo(text: str) -> None {
333
- response = root spawn create_todo(text=text);
334
- new_todo = response.reports[0][0];
335
- setTodos(todos.concat([new_todo]));
336
- }
337
-
338
- async def toggleTodo(id: str) -> None {
339
- id spawn toggle_todo();
340
- setTodos(todos.map(lambda todo: any -> any {
341
- if todo._jac_id == id {
342
- return {...todo, "done": not todo.done};
343
- }
344
- return todo;
345
- }));
346
- }
347
-
348
- useEffect(lambda -> None {
349
- loadTodos();
350
- }, []);
351
-
352
- return {
353
- "todos": todos,
354
- "loading": loading,
355
- "addTodo": addTodo,
356
- "toggleTodo": toggleTodo,
357
- "reload": loadTodos
358
- };
359
- }
360
-
361
- # Using custom hooks in components
362
- def TodoApp() -> any {
363
- userData = useUser();
364
- todoData = useTodos();
365
-
366
- if userData.loading or todoData.loading {
367
- return <div>Loading...</div>;
368
- }
369
-
370
- return <div>
371
- <h1>Welcome, {userData.user.name if userData.user else "Guest"}</h1>
372
- <button onClick={userData.logout}>Logout</button>
373
- {todoData.todos.map(lambda todo: any -> any {
374
- return <TodoItem key={todo._jac_id} todo={todo} />;
375
- })}
376
- </div>;
377
- }
378
-
379
- def app() -> any {
380
- return <TodoApp />;
381
- }
382
- }
383
- ```
384
-
385
- ---
386
-
387
- ## Derived State
388
-
389
- ### Computed Values with useMemo
390
-
391
- Use `useMemo` to memoize expensive computations:
392
-
393
- ```jac
394
- cl import from react { useState, useMemo }
395
-
396
- cl {
397
- def TodoApp() -> any {
398
- let [todos, setTodos] = useState([]);
399
- let [filter, setFilter] = useState("all");
400
-
401
- # Memoized filtered todos - only recomputes when todos or filter changes
402
- filteredTodos = useMemo(lambda -> list {
403
- if filter == "active" {
404
- return todos.filter(lambda item: any -> bool { return not item.done; });
405
- } elif filter == "completed" {
406
- return todos.filter(lambda item: any -> bool { return item.done; });
407
- }
408
- return todos;
409
- }, [todos, filter]);
410
-
411
- # Memoized stats - only recomputes when todos changes
412
- stats = useMemo(lambda -> dict {
413
- total = todos.length;
414
- active = todos.filter(lambda item: any -> bool { return not item.done; }).length;
415
- completed = total - active;
416
-
417
- return {
418
- "total": total,
419
- "active": active,
420
- "completed": completed
421
- };
422
- }, [todos]);
423
-
424
- return <div>
425
- <div>
426
- Total: {stats.total}, Active: {stats.active}, Completed: {stats.completed}
427
- </div>
428
- {filteredTodos.map(lambda item: any -> any {
429
- return <TodoItem key={item._jac_id} todo={item} />;
430
- })}
431
- </div>;
432
- }
433
-
434
- def app() -> any {
435
- return <TodoApp />;
436
- }
437
- }
438
- ```
439
-
440
- ### Simple Derived Values
441
-
442
- For simple computations, you don't need `useMemo`:
443
-
444
- ```jac
445
- cl import from react { useState }
446
-
447
- cl {
448
- def TodoApp() -> any {
449
- let [todos, setTodos] = useState([]);
450
- let [filter, setFilter] = useState("all");
451
-
452
- # Simple derived values - computed on each render
453
- def getFilteredTodos() -> list {
454
- if filter == "active" {
455
- return todos.filter(lambda item: any -> bool { return not item.done; });
456
- } elif filter == "completed" {
457
- return todos.filter(lambda item: any -> bool { return item.done; });
458
- }
459
- return todos;
460
- }
461
-
462
- filtered = getFilteredTodos();
463
- activeCount = todos.filter(lambda item: any -> bool { return not item.done; }).length;
464
-
465
- return <div>
466
- <div>{activeCount} active todos</div>
467
- {filtered.map(lambda item: any -> any {
468
- return <TodoItem key={item._jac_id} todo={item} />;
469
- })}
470
- </div>;
471
- }
472
-
473
- def app() -> any {
474
- return <TodoApp />;
475
- }
476
- }
477
- ```
478
-
479
- ### Reactive Updates with useEffect
480
-
481
- Use `useEffect` to sync derived state or perform side effects:
482
-
483
- ```jac
484
- cl import from react { useState, useEffect }
485
-
486
- cl {
487
- def TodoApp() -> any {
488
- let [todos, setTodos] = useState([]);
489
- let [stats, setStats] = useState({
490
- "total": 0,
491
- "active": 0,
492
- "completed": 0
493
- });
494
-
495
- # Update stats whenever todos change
496
- useEffect(lambda -> None {
497
- total = todos.length;
498
- active = todos.filter(lambda item: any -> bool { return not item.done; }).length;
499
- completed = total - active;
500
-
501
- setStats({
502
- "total": total,
503
- "active": active,
504
- "completed": completed
505
- });
506
-
507
- # Optional: Save to localStorage
508
- localStorage.setItem("todoStats", JSON.stringify(stats));
509
- }, [todos]);
510
-
511
- return <div>
512
- <StatsDisplay stats={stats} />
513
- {todos.map(lambda item: any -> any {
514
- return <TodoItem key={item._jac_id} todo={item} />;
515
- })}
516
- </div>;
517
- }
518
-
519
- def app() -> any {
520
- return <TodoApp />;
521
- }
522
- }
523
- ```
524
-
525
- ---
526
-
527
- ## Advanced React Hooks
528
-
529
- ### useReducer for Complex State
530
-
531
- When state logic becomes complex, use `useReducer` instead of `useState`:
532
-
533
- ```jac
534
- cl import from react { useReducer, useEffect }
535
-
536
- cl {
537
- # Reducer function
538
- def todoReducer(state: dict, action: dict) -> dict {
539
- type = action.type;
540
-
541
- if type == "ADD_TODO" {
542
- return {...state, "todos": state.todos.concat([action.payload])};
543
- } elif type == "TOGGLE_TODO" {
544
- return {
545
- ...state,
546
- "todos": state.todos.map(lambda todo: any -> any {
547
- if todo._jac_id == action.payload {
548
- return {...todo, "done": not todo.done};
549
- }
550
- return todo;
551
- })
552
- };
553
- } elif type == "REMOVE_TODO" {
554
- return {
555
- ...state,
556
- "todos": state.todos.filter(lambda todo: any -> bool {
557
- return todo._jac_id != action.payload;
558
- })
559
- };
560
- } elif type == "SET_FILTER" {
561
- return {...state, "filter": action.payload};
562
- } elif type == "SET_LOADING" {
563
- return {...state, "loading": action.payload};
564
- }
565
-
566
- return state;
567
- }
568
-
569
- def TodoApp() -> any {
570
- # Initial state
571
- initialState = {
572
- "todos": [],
573
- "filter": "all",
574
- "loading": False
575
- };
576
-
577
- let [state, dispatch] = useReducer(todoReducer, initialState);
578
-
579
- useEffect(lambda -> None {
580
- async def loadTodos() -> None {
581
- dispatch({"type": "SET_LOADING", "payload": True});
582
- result = root spawn read_todos();
583
- for todo in result.reports {
584
- dispatch({"type": "ADD_TODO", "payload": todo});
585
- }
586
- dispatch({"type": "SET_LOADING", "payload": False});
587
- }
588
- loadTodos();
589
- }, []);
590
-
591
- async def addTodo(text: str) -> None {
592
- response = root spawn create_todo(text=text);
593
- new_todo = response.reports[0][0];
594
- dispatch({"type": "ADD_TODO", "payload": new_todo});
595
- }
596
-
597
- def toggleTodo(id: str) -> None {
598
- dispatch({"type": "TOGGLE_TODO", "payload": id});
599
- }
600
-
601
- return <div>
602
- {state.loading and <div>Loading...</div>}
603
- {state.todos.map(lambda todo: any -> any {
604
- return <TodoItem
605
- key={todo._jac_id}
606
- todo={todo}
607
- onToggle={lambda -> None { toggleTodo(todo._jac_id); }}
608
- />;
609
- })}
610
- </div>;
611
- }
612
-
613
- def app() -> any {
614
- return <TodoApp />;
615
- }
616
- }
617
- ```
618
-
619
- ### useContext for Global State
620
-
621
- Share state across multiple components without prop drilling:
622
-
623
- ```jac
624
- cl import from react { useState, useContext, createContext }
625
-
626
- cl {
627
- # Create context
628
- TodoContext = createContext(None);
629
-
630
- # Provider component
631
- def TodoProvider(props: dict) -> any {
632
- let [todos, setTodos] = useState([]);
633
- let [filter, setFilter] = useState("all");
634
-
635
- async def addTodo(text: str) -> None {
636
- response = root spawn create_todo(text=text);
637
- new_todo = response.reports[0][0];
638
- setTodos(todos.concat([new_todo]));
639
- }
640
-
641
- async def toggleTodo(id: str) -> None {
642
- id spawn toggle_todo();
643
- setTodos(todos.map(lambda todo: any -> any {
644
- if todo._jac_id == id {
645
- return {...todo, "done": not todo.done};
646
- }
647
- return todo;
648
- }));
649
- }
650
-
651
- value = {
652
- "todos": todos,
653
- "filter": filter,
654
- "setFilter": setFilter,
655
- "addTodo": addTodo,
656
- "toggleTodo": toggleTodo
657
- };
658
-
659
- return <TodoContext.Provider value={value}>
660
- {props.children}
661
- </TodoContext.Provider>;
662
- }
663
-
664
- # Hook to use context
665
- def useTodoContext() -> dict {
666
- return useContext(TodoContext);
667
- }
668
-
669
- # Components using the context
670
- def TodoList() -> any {
671
- ctx = useTodoContext();
672
-
673
- filteredTodos = ctx.todos.filter(lambda todo: any -> bool {
674
- if ctx.filter == "active" { return not todo.done; }
675
- if ctx.filter == "completed" { return todo.done; }
676
- return True;
677
- });
678
-
679
- return <div>
680
- {filteredTodos.map(lambda todo: any -> any {
681
- return <TodoItem
682
- key={todo._jac_id}
683
- todo={todo}
684
- onToggle={lambda -> None { ctx.toggleTodo(todo._jac_id); }}
685
- />;
686
- })}
687
- </div>;
688
- }
689
-
690
- def FilterButtons() -> any {
691
- ctx = useTodoContext();
692
-
693
- return <div>
694
- <button onClick={lambda -> None { ctx.setFilter("all"); }}>All</button>
695
- <button onClick={lambda -> None { ctx.setFilter("active"); }}>Active</button>
696
- <button onClick={lambda -> None { ctx.setFilter("completed"); }}>Completed</button>
697
- </div>;
698
- }
699
-
700
- # App with provider
701
- def MainApp() -> any {
702
- return <TodoProvider>
703
- <FilterButtons />
704
- <TodoList />
705
- </TodoProvider>;
706
- }
707
-
708
- def app() -> any {
709
- return <MainApp />;
710
- }
711
- }
712
- ```
713
-
714
- ### useCallback for Memoized Functions
715
-
716
- Prevent unnecessary re-renders by memoizing callbacks:
717
-
718
- ```jac
719
- cl import from react { useState, useCallback }
720
-
721
- cl {
722
- def TodoApp() -> any {
723
- let [todos, setTodos] = useState([]);
724
-
725
- # Memoized callback - only recreated if todos changes
726
- handleToggle = useCallback(lambda id: str -> None {
727
- setTodos(todos.map(lambda todo: any -> any {
728
- if todo._jac_id == id {
729
- return {...todo, "done": not todo.done};
730
- }
731
- return todo;
732
- }));
733
- }, [todos]);
734
-
735
- return <div>
736
- {todos.map(lambda todo: any -> any {
737
- return <TodoItem
738
- key={todo._jac_id}
739
- todo={todo}
740
- onToggle={handleToggle}
741
- />;
742
- })}
743
- </div>;
744
- }
745
-
746
- def app() -> any {
747
- return <TodoApp />;
748
- }
749
- }
750
- ```
751
-
752
- ---
753
-
754
- ## State Management Patterns
755
-
756
- ### Pattern 1: Action Functions
757
-
758
- Encapsulate state logic in reusable action functions:
759
-
760
- ```jac
761
- cl import from react { useState }
762
-
763
- cl {
764
- def TodoApp() -> any {
765
- let [todos, setTodos] = useState([]);
766
- let [filter, setFilter] = useState("all");
767
-
768
- # Action functions
769
- async def addTodo(text: str) -> None {
770
- if not text.trim() { return; }
771
- response = root spawn create_todo(text=text);
772
- new_todo = response.reports[0][0];
773
- setTodos(todos.concat([new_todo]));
774
- }
775
-
776
- async def toggleTodo(id: str) -> None {
777
- id spawn toggle_todo();
778
- setTodos(todos.map(lambda todo: any -> any {
779
- if todo._jac_id == id {
780
- return {...todo, "done": not todo.done};
781
- }
782
- return todo;
783
- }));
784
- }
785
-
786
- def removeTodo(id: str) -> None {
787
- setTodos(todos.filter(lambda todo: any -> bool {
788
- return todo._jac_id != id;
789
- }));
790
- }
791
-
792
- def clearCompleted() -> None {
793
- setTodos(todos.filter(lambda todo: any -> bool {
794
- return not todo.done;
795
- }));
796
- }
797
-
798
- return <div>
799
- {/* UI using these actions */}
800
- </div>;
801
- }
802
-
803
- def app() -> any {
804
- return <TodoApp />;
805
- }
806
- }
807
- ```
808
-
809
- ### Pattern 2: Selector Functions with useMemo
810
-
811
- Create memoized selector functions for derived data:
812
-
813
- ```jac
814
- cl import from react { useState, useMemo }
815
-
816
- cl {
817
- def TodoApp() -> any {
818
- let [todos, setTodos] = useState([]);
819
- let [filter, setFilter] = useState("all");
820
-
821
- # Memoized selectors
822
- filteredTodos = useMemo(lambda -> list {
823
- if filter == "active" {
824
- return todos.filter(lambda t: any -> bool { return not t.done; });
825
- } elif filter == "completed" {
826
- return todos.filter(lambda t: any -> bool { return t.done; });
827
- }
828
- return todos;
829
- }, [todos, filter]);
830
-
831
- activeTodos = useMemo(lambda -> list {
832
- return todos.filter(lambda t: any -> bool { return not t.done; });
833
- }, [todos]);
834
-
835
- completedTodos = useMemo(lambda -> list {
836
- return todos.filter(lambda t: any -> bool { return t.done; });
837
- }, [todos]);
838
-
839
- return <div>
840
- <div>Active: {activeTodos.length}</div>
841
- <div>Completed: {completedTodos.length}</div>
842
- {filteredTodos.map(lambda todo: any -> any {
843
- return <TodoItem key={todo._jac_id} todo={todo} />;
844
- })}
845
- </div>;
846
- }
847
-
848
- def app() -> any {
849
- return <TodoApp />;
850
- }
851
- }
852
- ```
853
-
854
- ### Pattern 3: Combining Multiple Hooks
855
-
856
- Combine useState, useReducer, and useContext for complex applications:
857
-
858
- ```jac
859
- cl import from react { useState, useReducer, useContext, createContext, useEffect }
860
-
861
- cl {
862
- # Context for global state
863
- AppContext = createContext(None);
864
-
865
- # Main app with combined hooks
866
- def App() -> any {
867
- # User state with useState
868
- let [user, setUser] = useState(None);
869
-
870
- # Todo state with useReducer
871
- def todoReducer(state: dict, action: dict) -> dict {
872
- if action.type == "ADD" {
873
- return {...state, "todos": state.todos.concat([action.payload])};
874
- } elif action.type == "TOGGLE" {
875
- return {
876
- ...state,
877
- "todos": state.todos.map(lambda t: any -> any {
878
- if t._jac_id == action.payload {
879
- return {...t, "done": not t.done};
880
- }
881
- return t;
882
- })
883
- };
884
- }
885
- return state;
886
- }
887
-
888
- let [todoState, dispatch] = useReducer(todoReducer, {"todos": [], "loading": False});
889
-
890
- # UI state with useState
891
- let [theme, setTheme] = useState("light");
892
-
893
- useEffect(lambda -> None {
894
- async def loadData() -> None {
895
- userData = root spawn get_user();
896
- setUser(userData);
897
- }
898
- loadData();
899
- }, []);
900
-
901
- contextValue = {
902
- "user": user,
903
- "setUser": setUser,
904
- "todoState": todoState,
905
- "dispatch": dispatch,
906
- "theme": theme,
907
- "setTheme": setTheme
908
- };
909
-
910
- return <AppContext.Provider value={contextValue}>
911
- <TodoList />
912
- </AppContext.Provider>;
913
- }
914
-
915
- def app() -> any {
916
- return <App />;
917
- }
918
- }
919
- ```
920
-
921
- ---
922
-
923
- ## Complete Example: Full-Featured Todo App
924
-
925
- Here's a complete example combining multiple React hooks and patterns:
926
-
927
- ```jac
928
- cl import from react { useState, useEffect, useMemo, useCallback }
929
-
930
- cl {
931
- def TodoApp() -> any {
932
- # State management
933
- let [todos, setTodos] = useState([]);
934
- let [filter, setFilter] = useState("all");
935
- let [loading, setLoading] = useState(False);
936
- let [error, setError] = useState(None);
937
- let [user, setUser] = useState(None);
938
- let [sidebarOpen, setSidebarOpen] = useState(False);
939
-
940
- # Load initial data
941
- useEffect(lambda -> None {
942
- async def loadData() -> None {
943
- setLoading(True);
944
- setError(None);
945
- try {
946
- # Load user and todos in parallel
947
- results = await Promise.all([
948
- root spawn get_current_user(),
949
- root spawn read_todos()
950
- ]);
951
- setUser(results[0]);
952
- setTodos(results[1].reports);
953
- } catch (err) {
954
- setError(err);
955
- } finally {
956
- setLoading(False);
957
- }
958
- }
959
- loadData();
960
- }, []);
961
-
962
- # Memoized derived state
963
- filteredTodos = useMemo(lambda -> list {
964
- if filter == "active" {
965
- return todos.filter(lambda t: any -> bool { return not t.done; });
966
- } elif filter == "completed" {
967
- return todos.filter(lambda t: any -> bool { return t.done; });
968
- }
969
- return todos;
970
- }, [todos, filter]);
971
-
972
- stats = useMemo(lambda -> dict {
973
- total = todos.length;
974
- active = todos.filter(lambda t: any -> bool { return not t.done; }).length;
975
- return {"total": total, "active": active, "completed": total - active};
976
- }, [todos]);
977
-
978
- # Memoized action functions
979
- addTodo = useCallback(lambda text: str -> None {
980
- async def _addTodo() -> None {
981
- response = root spawn create_todo(text=text);
982
- new_todo = response.reports[0][0];
983
- setTodos(todos.concat([new_todo]));
984
- }
985
- _addTodo();
986
- }, [todos]);
987
-
988
- toggleTodo = useCallback(lambda id: str -> None {
989
- async def _toggleTodo() -> None {
990
- id spawn toggle_todo();
991
- setTodos(todos.map(lambda t: any -> any {
992
- if t._jac_id == id {
993
- return {...t, "done": not t.done};
994
- }
995
- return t;
996
- }));
997
- }
998
- _toggleTodo();
999
- }, [todos]);
1000
-
1001
- removeTodo = useCallback(lambda id: str -> None {
1002
- setTodos(todos.filter(lambda t: any -> bool { return t._jac_id != id; }));
1003
- }, [todos]);
1004
-
1005
- clearCompleted = useCallback(lambda -> None {
1006
- setTodos(todos.filter(lambda t: any -> bool { return not t.done; }));
1007
- }, [todos]);
1008
-
1009
- toggleSidebar = useCallback(lambda -> None {
1010
- setSidebarOpen(not sidebarOpen);
1011
- }, [sidebarOpen]);
1012
-
1013
- # Render
1014
- if loading {
1015
- return <div style={{"padding": "20px"}}>Loading...</div>;
1016
- }
1017
-
1018
- if error {
1019
- return <div style={{"padding": "20px", "color": "red"}}>
1020
- Error: {error}
1021
- </div>;
1022
- }
1023
-
1024
- return <div style={{"display": "flex", "minHeight": "100vh"}}>
1025
- # Sidebar
1026
- {sidebarOpen and <div style={{"width": "250px", "padding": "20px", "background": "#f5f5f5"}}>
1027
- <h3>Filter</h3>
1028
- <button onClick={lambda -> None { setFilter("all"); }}>All ({stats.total})</button>
1029
- <button onClick={lambda -> None { setFilter("active"); }}>Active ({stats.active})</button>
1030
- <button onClick={lambda -> None { setFilter("completed"); }}>Completed ({stats.completed})</button>
1031
- </div>}
1032
-
1033
- # Main content
1034
- <div style={{"flex": "1", "padding": "20px"}}>
1035
- # Header
1036
- <div style={{"display": "flex", "justifyContent": "space-between", "marginBottom": "20px"}}>
1037
- <h1>Welcome, {user.name if user else "Guest"}</h1>
1038
- <button onClick={toggleSidebar}>
1039
- {"Hide" if sidebarOpen else "Show"} Sidebar
1040
- </button>
1041
- </div>
1042
-
1043
- # Stats
1044
- <div style={{"marginBottom": "20px"}}>
1045
- {stats.active} active, {stats.completed} completed, {stats.total} total
1046
- </div>
1047
-
1048
- # Todo list
1049
- <div>
1050
- {filteredTodos.map(lambda todo: any -> any {
1051
- return <div key={todo._jac_id} style={{"marginBottom": "10px"}}>
1052
- <input
1053
- type="checkbox"
1054
- checked={todo.done}
1055
- onChange={lambda -> None { toggleTodo(todo._jac_id); }}
1056
- />
1057
- <span style={{"marginLeft": "10px"}}>{todo.text}</span>
1058
- <button
1059
- onClick={lambda -> None { removeTodo(todo._jac_id); }}
1060
- style={{"marginLeft": "10px"}}
1061
- >
1062
- Delete
1063
- </button>
1064
- </div>;
1065
- })}
1066
- </div>
1067
-
1068
- # Clear completed button
1069
- {stats.completed > 0 and <button
1070
- onClick={clearCompleted}
1071
- style={{"marginTop": "20px"}}
1072
- >
1073
- Clear Completed
1074
- </button>}
1075
- </div>
1076
- </div>;
1077
- }
1078
-
1079
- def app() -> any {
1080
- return <TodoApp />;
1081
- }
1082
- }
1083
- ```
1084
-
1085
- ---
1086
-
1087
- ## Best Practices
1088
-
1089
- ### 1. Separate State Variables by Concern
1090
-
1091
- ```jac
1092
- cl import from react { useState }
1093
-
1094
- # Good: Separate state variables
1095
- def App() -> any {
1096
- let [user, setUser] = useState(None);
1097
- let [todos, setTodos] = useState([]);
1098
- let [sidebarOpen, setSidebarOpen] = useState(False);
1099
- let [theme, setTheme] = useState("light");
1100
- }
1101
-
1102
- # Avoid: One giant state object for unrelated data
1103
- def App() -> any {
1104
- let [appState, setAppState] = useState({
1105
- "user": None,
1106
- "todos": [],
1107
- "sidebarOpen": False,
1108
- "theme": "light"
1109
- });
1110
- }
1111
- ```
1112
-
1113
- ### 2. Use useMemo for Expensive Computations
1114
-
1115
- ```jac
1116
- cl import from react { useState, useMemo }
1117
-
1118
- # Good: Memoize expensive calculations
1119
- def TodoApp() -> any {
1120
- let [todos, setTodos] = useState([]);
1121
-
1122
- activeTodos = useMemo(lambda -> list {
1123
- return todos.filter(lambda t: any -> bool { return not t.done; });
1124
- }, [todos]);
1125
- }
1126
-
1127
- # Avoid: Computing on every render
1128
- def TodoApp() -> any {
1129
- let [todos, setTodos] = useState([]);
1130
-
1131
- # This runs on every render, even if todos hasn't changed
1132
- activeTodos = todos.filter(lambda t: any -> bool { return not t.done; });
1133
- }
1134
- ```
1135
-
1136
- ### 3. Don't Store Derived State
1137
-
1138
- ```jac
1139
- cl import from react { useState }
1140
-
1141
- # Good: Calculate derived values
1142
- def TodoApp() -> any {
1143
- let [todos, setTodos] = useState([]);
1144
- activeCount = todos.filter(lambda t: any -> bool { return not t.done; }).length;
1145
- }
1146
-
1147
- # Avoid: Storing derived values in state
1148
- def TodoApp() -> any {
1149
- let [todos, setTodos] = useState([]);
1150
- let [activeCount, setActiveCount] = useState(0); # Redundant!
1151
- }
1152
- ```
1153
-
1154
- ### 4. Use useReducer for Complex State Logic
1155
-
1156
- ```jac
1157
- cl import from react { useReducer }
1158
-
1159
- # Good: useReducer for complex interdependent state
1160
- def TodoApp() -> any {
1161
- def reducer(state: dict, action: dict) -> dict {
1162
- if action.type == "ADD" {
1163
- return {...state, "todos": state.todos.concat([action.payload]), "count": state.count + 1};
1164
- }
1165
- return state;
1166
- }
1167
-
1168
- let [state, dispatch] = useReducer(reducer, {"todos": [], "count": 0});
1169
- }
1170
-
1171
- # Avoid: Multiple useState for interdependent state
1172
- def TodoApp() -> any {
1173
- let [todos, setTodos] = useState([]);
1174
- let [count, setCount] = useState(0);
1175
- # Risk of inconsistency - need to update both together
1176
- }
1177
- ```
1178
-
1179
- ### 5. Always Handle Loading and Error States
1180
-
1181
- ```jac
1182
- cl import from react { useState, useEffect }
1183
-
1184
- # Good: Comprehensive state management
1185
- def TodoApp() -> any {
1186
- let [todos, setTodos] = useState([]);
1187
- let [loading, setLoading] = useState(False);
1188
- let [error, setError] = useState(None);
1189
-
1190
- useEffect(lambda -> None {
1191
- async def loadTodos() -> None {
1192
- setLoading(True);
1193
- setError(None);
1194
- try {
1195
- result = root spawn read_todos();
1196
- setTodos(result.reports);
1197
- } catch (err) {
1198
- setError(err);
1199
- } finally {
1200
- setLoading(False);
1201
- }
1202
- }
1203
- loadTodos();
1204
- }, []);
1205
-
1206
- if loading { return <div>Loading...</div>; }
1207
- if error { return <div>Error: {error}</div>; }
1208
- return <div>{/* todos */}</div>;
1209
- }
1210
- ```
1211
-
1212
- ### 6. Use useCallback for Callbacks Passed to Children
1213
-
1214
- ```jac
1215
- cl import from react { useState, useCallback }
1216
-
1217
- # Good: Memoized callbacks prevent unnecessary re-renders
1218
- def TodoApp() -> any {
1219
- let [todos, setTodos] = useState([]);
1220
-
1221
- handleToggle = useCallback(lambda id: str -> None {
1222
- setTodos(todos.map(lambda t: any -> any {
1223
- if t._jac_id == id { return {...t, "done": not t.done}; }
1224
- return t;
1225
- }));
1226
- }, [todos]);
1227
-
1228
- return <TodoList todos={todos} onToggle={handleToggle} />;
1229
- }
1230
- ```
1231
-
1232
- ### 7. Use Context for Deeply Nested Props
1233
-
1234
- ```jac
1235
- cl import from react { useState, useContext, createContext }
1236
-
1237
- # Good: Context avoids prop drilling
1238
- ThemeContext = createContext("light");
1239
-
1240
- def App() -> any {
1241
- let [theme, setTheme] = useState("light");
1242
- return <ThemeContext.Provider value={theme}>
1243
- <DeeplyNestedComponent />
1244
- </ThemeContext.Provider>;
1245
- }
1246
-
1247
- def DeeplyNestedComponent() -> any {
1248
- theme = useContext(ThemeContext);
1249
- return <div style={{"background": theme}}></div>;
1250
- }
1251
- ```
1252
-
1253
- ---
1254
-
1255
- ## Summary
1256
-
1257
- - **useState**: Use for simple, independent state variables
1258
- - **useReducer**: Use for complex, interdependent state logic
1259
- - **useContext**: Use for global state and avoiding prop drilling
1260
- - **useMemo**: Use to memoize expensive computations
1261
- - **useCallback**: Use to memoize callbacks passed to child components
1262
- - **Custom Hooks**: Create reusable state logic
1263
- - **Best Practices**: Separate concerns, avoid derived state, handle errors
1264
-
1265
- React hooks provide a powerful and flexible way to manage state in Jac applications!