jac-client 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. jac_client/docs/README.md +232 -172
  2. jac_client/docs/advanced-state.md +1012 -452
  3. jac_client/docs/asset-serving/intro.md +209 -0
  4. jac_client/docs/assets/pipe_line-v2.svg +32 -0
  5. jac_client/docs/assets/pipe_line.png +0 -0
  6. jac_client/docs/file-system/intro.md +90 -0
  7. jac_client/docs/guide-example/intro.md +117 -0
  8. jac_client/docs/guide-example/step-01-setup.md +260 -0
  9. jac_client/docs/guide-example/step-02-components.md +416 -0
  10. jac_client/docs/guide-example/step-03-styling.md +478 -0
  11. jac_client/docs/guide-example/step-04-todo-ui.md +477 -0
  12. jac_client/docs/guide-example/step-05-local-state.md +530 -0
  13. jac_client/docs/guide-example/step-06-events.md +750 -0
  14. jac_client/docs/guide-example/step-07-effects.md +469 -0
  15. jac_client/docs/guide-example/step-08-walkers.md +534 -0
  16. jac_client/docs/guide-example/step-09-authentication.md +586 -0
  17. jac_client/docs/guide-example/step-10-routing.md +540 -0
  18. jac_client/docs/guide-example/step-11-final.md +964 -0
  19. jac_client/docs/imports.md +538 -46
  20. jac_client/docs/lifecycle-hooks.md +517 -297
  21. jac_client/docs/routing.md +487 -357
  22. jac_client/docs/styling/intro.md +250 -0
  23. jac_client/docs/styling/js-styling.md +373 -0
  24. jac_client/docs/styling/material-ui.md +346 -0
  25. jac_client/docs/styling/pure-css.md +305 -0
  26. jac_client/docs/styling/sass.md +409 -0
  27. jac_client/docs/styling/styled-components.md +401 -0
  28. jac_client/docs/styling/tailwind.md +303 -0
  29. jac_client/examples/asset-serving/css-with-image/.babelrc +9 -0
  30. jac_client/examples/asset-serving/css-with-image/README.md +91 -0
  31. jac_client/examples/asset-serving/css-with-image/app.jac +67 -0
  32. jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
  33. jac_client/examples/asset-serving/css-with-image/package.json +28 -0
  34. jac_client/examples/asset-serving/css-with-image/styles.css +27 -0
  35. jac_client/examples/asset-serving/css-with-image/vite.config.js +29 -0
  36. jac_client/examples/asset-serving/image-asset/.babelrc +9 -0
  37. jac_client/examples/asset-serving/image-asset/README.md +119 -0
  38. jac_client/examples/asset-serving/image-asset/app.jac +43 -0
  39. jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
  40. jac_client/examples/asset-serving/image-asset/package.json +28 -0
  41. jac_client/examples/asset-serving/image-asset/styles.css +27 -0
  42. jac_client/examples/asset-serving/image-asset/vite.config.js +29 -0
  43. jac_client/examples/asset-serving/import-alias/.babelrc +9 -0
  44. jac_client/examples/asset-serving/import-alias/README.md +83 -0
  45. jac_client/examples/asset-serving/import-alias/app.jac +57 -0
  46. jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
  47. jac_client/examples/asset-serving/import-alias/package.json +28 -0
  48. jac_client/examples/asset-serving/import-alias/vite.config.js +29 -0
  49. jac_client/examples/basic/.babelrc +9 -0
  50. jac_client/examples/basic/README.md +16 -0
  51. jac_client/examples/basic/app.jac +16 -0
  52. jac_client/examples/basic/package.json +27 -0
  53. jac_client/examples/basic/vite.config.js +28 -0
  54. jac_client/examples/basic-auth/.babelrc +9 -0
  55. jac_client/examples/basic-auth/README.md +16 -0
  56. jac_client/examples/basic-auth/app.jac +308 -0
  57. jac_client/examples/basic-auth/package.json +27 -0
  58. jac_client/examples/basic-auth/vite.config.js +28 -0
  59. jac_client/examples/basic-auth-with-router/.babelrc +9 -0
  60. jac_client/examples/basic-auth-with-router/README.md +60 -0
  61. jac_client/examples/basic-auth-with-router/app.jac +464 -0
  62. jac_client/examples/basic-auth-with-router/package.json +28 -0
  63. jac_client/examples/basic-auth-with-router/vite.config.js +28 -0
  64. jac_client/examples/basic-full-stack/.babelrc +9 -0
  65. jac_client/examples/basic-full-stack/README.md +18 -0
  66. jac_client/examples/basic-full-stack/app.jac +320 -0
  67. jac_client/examples/basic-full-stack/package.json +28 -0
  68. jac_client/examples/basic-full-stack/vite.config.js +28 -0
  69. jac_client/examples/css-styling/js-styling/.babelrc +9 -0
  70. jac_client/examples/css-styling/js-styling/README.md +183 -0
  71. jac_client/examples/css-styling/js-styling/app.jac +63 -0
  72. jac_client/examples/css-styling/js-styling/package.json +28 -0
  73. jac_client/examples/css-styling/js-styling/styles.js +100 -0
  74. jac_client/examples/css-styling/js-styling/vite.config.js +28 -0
  75. jac_client/examples/css-styling/material-ui/.babelrc +9 -0
  76. jac_client/examples/css-styling/material-ui/README.md +16 -0
  77. jac_client/examples/css-styling/material-ui/app.jac +82 -0
  78. jac_client/examples/css-styling/material-ui/package.json +32 -0
  79. jac_client/examples/css-styling/material-ui/vite.config.js +28 -0
  80. jac_client/examples/css-styling/pure-css/.babelrc +9 -0
  81. jac_client/examples/css-styling/pure-css/README.md +16 -0
  82. jac_client/examples/css-styling/pure-css/app.jac +63 -0
  83. jac_client/examples/css-styling/pure-css/package.json +28 -0
  84. jac_client/examples/css-styling/pure-css/styles.css +112 -0
  85. jac_client/examples/css-styling/pure-css/vite.config.js +28 -0
  86. jac_client/examples/css-styling/sass-example/.babelrc +9 -0
  87. jac_client/examples/css-styling/sass-example/README.md +16 -0
  88. jac_client/examples/css-styling/sass-example/app.jac +63 -0
  89. jac_client/examples/css-styling/sass-example/package.json +29 -0
  90. jac_client/examples/css-styling/sass-example/styles.scss +158 -0
  91. jac_client/examples/css-styling/sass-example/vite.config.js +28 -0
  92. jac_client/examples/css-styling/styled-components/.babelrc +9 -0
  93. jac_client/examples/css-styling/styled-components/README.md +16 -0
  94. jac_client/examples/css-styling/styled-components/app.jac +66 -0
  95. jac_client/examples/css-styling/styled-components/package.json +29 -0
  96. jac_client/examples/css-styling/styled-components/styled.js +91 -0
  97. jac_client/examples/css-styling/styled-components/vite.config.js +28 -0
  98. jac_client/examples/css-styling/tailwind-example/.babelrc +9 -0
  99. jac_client/examples/css-styling/tailwind-example/README.md +16 -0
  100. jac_client/examples/css-styling/tailwind-example/app.jac +64 -0
  101. jac_client/examples/css-styling/tailwind-example/global.css +1 -0
  102. jac_client/examples/css-styling/tailwind-example/package.json +30 -0
  103. jac_client/examples/css-styling/tailwind-example/vite.config.js +30 -0
  104. jac_client/examples/full-stack-with-auth/.babelrc +9 -0
  105. jac_client/examples/full-stack-with-auth/README.md +16 -0
  106. jac_client/examples/full-stack-with-auth/app.jac +735 -0
  107. jac_client/examples/full-stack-with-auth/package.json +28 -0
  108. jac_client/examples/full-stack-with-auth/vite.config.js +30 -0
  109. jac_client/examples/with-router/.babelrc +9 -0
  110. jac_client/examples/with-router/README.md +17 -0
  111. jac_client/examples/with-router/app.jac +323 -0
  112. jac_client/examples/with-router/package.json +28 -0
  113. jac_client/examples/with-router/vite.config.js +28 -0
  114. jac_client/plugin/cli.py +95 -179
  115. jac_client/plugin/client.py +111 -2
  116. jac_client/plugin/client_runtime.jac +183 -890
  117. jac_client/plugin/vite_client_bundle.py +185 -205
  118. jac_client/tests/__init__.py +0 -1
  119. jac_client/tests/fixtures/{client_app.jac → basic-app/app.jac} +1 -1
  120. jac_client/tests/fixtures/cl_file/app.cl.jac +38 -0
  121. jac_client/tests/fixtures/cl_file/app.jac +15 -0
  122. jac_client/tests/fixtures/{client_app_with_antd.jac → client_app_with_antd/app.jac} +7 -0
  123. jac_client/tests/fixtures/{js_import.jac → js_import/app.jac} +2 -2
  124. jac_client/tests/fixtures/{relative_import.jac → relative_import/app.jac} +1 -1
  125. jac_client/tests/fixtures/{button.jac → relative_import/button.jac} +2 -2
  126. jac_client/tests/fixtures/spawn_test/app.jac +133 -0
  127. jac_client/tests/fixtures/{test_fragments_spread.jac → test_fragments_spread/app.jac} +11 -2
  128. jac_client/tests/test_asset_examples.py +339 -0
  129. jac_client/tests/test_cl.py +345 -151
  130. jac_client/tests/test_create_jac_app.py +41 -45
  131. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/METADATA +72 -16
  132. jac_client-0.2.1.dist-info/RECORD +140 -0
  133. jac_client/examples/little-x/package-lock.json +0 -2840
  134. jac_client/examples/todo-app/README.md +0 -82
  135. jac_client/examples/todo-app/app.jac +0 -683
  136. jac_client/examples/todo-app/package-lock.json +0 -999
  137. jac_client/examples/todo-app/package.json +0 -22
  138. jac_client-0.1.0.dist-info/RECORD +0 -33
  139. /jac_client/tests/fixtures/{utils.js → js_import/utils.js} +0 -0
  140. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/WHEEL +0 -0
  141. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/entry_points.txt +0 -0
@@ -1,60 +1,147 @@
1
1
  # Advanced State Management in Jac
2
2
 
3
- Learn how to combine multiple `createState()` calls, manage complex state patterns, and build scalable state architectures.
3
+ Learn how to manage complex state in Jac using React hooks, combining multiple state instances, and building scalable state architectures.
4
4
 
5
5
  ---
6
6
 
7
7
  ## 📚 Table of Contents
8
8
 
9
- - [Multiple State Instances](#multiple-state-instances)
9
+ - [React Hooks Overview](#react-hooks-overview)
10
+ - [Multiple State Variables](#multiple-state-variables)
10
11
  - [State Composition Patterns](#state-composition-patterns)
11
12
  - [Derived State](#derived-state)
13
+ - [Advanced React Hooks](#advanced-react-hooks)
12
14
  - [State Management Patterns](#state-management-patterns)
13
15
  - [Best Practices](#best-practices)
14
16
 
15
17
  ---
16
18
 
17
- ## Multiple State Instances
19
+ ## React Hooks Overview
18
20
 
19
- ### Basic Multiple State Pattern
21
+ Jac uses React hooks for all state management. The most common hooks are:
20
22
 
21
- Instead of putting everything in one state object, you can split state into multiple instances:
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
22
31
 
23
32
  ```jac
33
+ cl import from react { useState, useEffect }
34
+
24
35
  cl {
25
- # Separate concerns into different state instances
26
- let [todoState, setTodoState] = createState({
27
- "items": []
28
- });
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
+ }, []);
29
50
 
30
- let [filterState, setFilterState] = createState({
31
- "filter": "all"
32
- });
51
+ return <div>{/* your UI */}</div>;
52
+ }
33
53
 
34
- let [uiState, setUiState] = createState({
35
- "loading": False,
36
- "error": None
37
- });
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 }
38
70
 
71
+ cl {
39
72
  def TodoApp() -> any {
40
- todos = todoState();
41
- filter = filterState();
42
- ui = uiState();
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>; }
43
97
 
44
98
  return <div>
45
- {ui.loading and <div>Loading...</div>}
46
- {ui.error and <div>Error: {ui.error}</div>}
47
- {[TodoItem(item) for item in todos.items]}
99
+ {todos.map(lambda todo: any -> any {
100
+ return <TodoItem key={todo._jac_id} todo={todo} />;
101
+ })}
48
102
  </div>;
49
103
  }
104
+
105
+ def app() -> any {
106
+ return <TodoApp />;
107
+ }
50
108
  }
51
109
  ```
52
110
 
53
111
  **Benefits:**
54
- - **Separation of Concerns**: Each state manages one aspect
112
+ - **Separation of Concerns**: Each state variable manages one aspect
55
113
  - **Selective Updates**: Only components using specific state re-render
56
- - **Easier Testing**: Test each state independently
57
- - **Better Organization**: Clearer code structure
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
+ ```
58
145
 
59
146
  ---
60
147
 
@@ -62,151 +149,235 @@ cl {
62
149
 
63
150
  ### Pattern 1: Feature-Based State
64
151
 
65
- Organize state by feature or domain:
152
+ Organize state by feature or domain using multiple `useState` calls:
66
153
 
67
154
  ```jac
68
- cl {
69
- # User state
70
- let [userState, setUserState] = createState({
71
- "profile": None,
72
- "isLoggedIn": False
73
- });
74
-
75
- # Todo state
76
- let [todoState, setTodoState] = createState({
77
- "items": [],
78
- "selectedId": None
79
- });
80
-
81
- # UI state
82
- let [uiState, setUiState] = createState({
83
- "theme": "light",
84
- "sidebarOpen": False,
85
- "modalOpen": False
86
- });
87
-
88
- # Settings state
89
- let [settingsState, setSettingsState] = createState({
90
- "notifications": True,
91
- "language": "en"
92
- });
155
+ cl import from react { useState, useEffect }
93
156
 
157
+ cl {
94
158
  def App() -> any {
95
- user = userState();
96
- todos = todoState();
97
- ui = uiState();
98
- settings = settingsState();
99
-
100
- return <div className={ui.theme}>
101
- {ui.sidebarOpen and <Sidebar />}
102
- {todos.items.length > 0 and <TodoList items={todos.items} />}
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} />}
103
188
  </div>;
104
189
  }
190
+
191
+ def app() -> any {
192
+ return <App />;
193
+ }
105
194
  }
106
195
  ```
107
196
 
108
197
  ### Pattern 2: Local vs Global State
109
198
 
110
- Use different state instances for different scopes:
199
+ Use Context for global state and `useState` for local state:
111
200
 
112
201
  ```jac
202
+ cl import from react { useState, useContext, createContext }
203
+
113
204
  cl {
114
- # Global application state
115
- let [appState, setAppState] = createState({
116
- "currentUser": None,
117
- "theme": "light"
118
- });
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
+ }
119
225
 
120
- # Component-specific state (can be defined inside components)
226
+ # Component with local state
121
227
  def TodoForm() -> any {
228
+ # Access global context
229
+ app = useContext(AppContext);
230
+
122
231
  # Local component state
123
- let [formState, setFormState] = createState({
124
- "text": "",
125
- "valid": False
126
- });
232
+ let [text, setText] = useState("");
233
+ let [valid, setValid] = useState(False);
127
234
 
128
- def validate() -> None {
129
- text = formState().text;
130
- setFormState({"valid": len(text.trim()) > 0});
235
+ def validate(value: str) -> None {
236
+ setValid(len(value.trim()) > 0);
131
237
  }
132
238
 
133
239
  return <form>
134
240
  <input
135
- value={formState().text}
241
+ value={text}
136
242
  onChange={lambda e: any -> None {
137
- setFormState({"text": e.target.value});
138
- validate();
243
+ newText = e.target.value;
244
+ setText(newText);
245
+ validate(newText);
139
246
  }}
247
+ style={{"background": ("#333" if app.theme == "dark" else "#fff")}}
140
248
  />
141
249
  </form>;
142
250
  }
143
251
 
144
252
  def TodoList() -> any {
145
253
  # Local list state
146
- let [listState, setListState] = createState({
147
- "sortBy": "date",
148
- "order": "asc"
149
- });
254
+ let [sortBy, setSortBy] = useState("date");
255
+ let [order, setOrder] = useState("asc");
150
256
 
151
- todos = appState().todos;
152
- sorted = sortTodos(todos, listState());
257
+ # Access global context
258
+ app = useContext(AppContext);
153
259
 
154
260
  return <div>
155
- {[TodoItem(item) for item in sorted]}
261
+ <h2>Welcome, {app.currentUser.name if app.currentUser else "Guest"}</h2>
156
262
  </div>;
157
263
  }
264
+
265
+ def app() -> any {
266
+ return <App />;
267
+ }
158
268
  }
159
269
  ```
160
270
 
161
- ### Pattern 3: State Modules
271
+ ### Pattern 3: Custom Hooks (State Modules)
162
272
 
163
- Create reusable state modules:
273
+ Create reusable custom hooks for shared logic:
164
274
 
165
275
  ```jac
276
+ cl import from react { useState, useEffect }
277
+ cl import from '@jac-client/utils' { jacLogout }
278
+
166
279
  cl {
167
- # State module: User management
168
- let [userState, setUserState] = createState({
169
- "currentUser": None,
170
- "isLoading": False,
171
- "error": None
172
- });
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
+ }
173
298
 
174
- async def loadUser() -> None {
175
- setUserState({"isLoading": True, "error": None});
176
- try {
177
- user = await __jacSpawn("get_current_user");
178
- setUserState({"currentUser": user, "isLoading": False});
179
- } except Exception as err {
180
- setUserState({"error": str(err), "isLoading": False});
299
+ def logout() -> None {
300
+ jacLogout();
301
+ setUser(None);
181
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
+ };
182
315
  }
183
316
 
184
- def logout() -> None {
185
- jacLogout();
186
- setUserState({"currentUser": None});
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
+ };
187
359
  }
188
360
 
189
- # State module: Todo management
190
- let [todoState, setTodoState] = createState({
191
- "items": [],
192
- "filter": "all",
193
- "loading": False
194
- });
361
+ # Using custom hooks in components
362
+ def TodoApp() -> any {
363
+ userData = useUser();
364
+ todoData = useTodos();
195
365
 
196
- async def loadTodos() -> None {
197
- setTodoState({"loading": True});
198
- try {
199
- todos = await __jacSpawn("read_todos");
200
- setTodoState({"items": todos.reports, "loading": False});
201
- } except Exception as err {
202
- setTodoState({"loading": False});
366
+ if userData.loading or todoData.loading {
367
+ return <div>Loading...</div>;
203
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>;
204
377
  }
205
378
 
206
- async def addTodo(text: str) -> None {
207
- new_todo = await __jacSpawn("create_todo", {"text": text});
208
- s = todoState();
209
- setTodoState({"items": s.items.concat([new_todo])});
379
+ def app() -> any {
380
+ return <TodoApp />;
210
381
  }
211
382
  }
212
383
  ```
@@ -215,389 +386,699 @@ cl {
215
386
 
216
387
  ## Derived State
217
388
 
218
- ### Computed Values from Multiple States
389
+ ### Computed Values with useMemo
219
390
 
220
- Combine multiple states to create derived values:
391
+ Use `useMemo` to memoize expensive computations:
221
392
 
222
393
  ```jac
223
- cl {
224
- let [todoState, setTodoState] = createState({
225
- "items": []
226
- });
394
+ cl import from react { useState, useMemo }
227
395
 
228
- let [filterState, setFilterState] = createState({
229
- "filter": "all"
230
- });
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]);
231
423
 
232
- def getFilteredTodos() -> list {
233
- todos = todoState();
234
- filter = filterState();
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
+ }
235
433
 
236
- if filter == "active" {
237
- return [item for item in todos.items if not item.done];
238
- } elif filter == "completed" {
239
- return [item for item in todos.items if item.done];
240
- }
241
- return todos.items;
434
+ def app() -> any {
435
+ return <TodoApp />;
242
436
  }
437
+ }
438
+ ```
243
439
 
244
- def getStats() -> dict {
245
- todos = todoState();
246
- total = len(todos.items);
247
- active = len([item for item in todos.items if not item.done]);
248
- completed = total - active;
440
+ ### Simple Derived Values
249
441
 
250
- return {
251
- "total": total,
252
- "active": active,
253
- "completed": completed
254
- };
255
- }
442
+ For simple computations, you don't need `useMemo`:
256
443
 
444
+ ```jac
445
+ cl import from react { useState }
446
+
447
+ cl {
257
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
+
258
462
  filtered = getFilteredTodos();
259
- stats = getStats();
463
+ activeCount = todos.filter(lambda item: any -> bool { return not item.done; }).length;
260
464
 
261
465
  return <div>
262
- <div>
263
- Total: {stats.total}, Active: {stats.active}, Completed: {stats.completed}
264
- </div>
265
- {[TodoItem(item) for item in filtered]}
466
+ <div>{activeCount} active todos</div>
467
+ {filtered.map(lambda item: any -> any {
468
+ return <TodoItem key={item._jac_id} todo={item} />;
469
+ })}
266
470
  </div>;
267
471
  }
472
+
473
+ def app() -> any {
474
+ return <TodoApp />;
475
+ }
268
476
  }
269
477
  ```
270
478
 
271
- ### Reactive Derived State
479
+ ### Reactive Updates with useEffect
272
480
 
273
- Use `createEffect()` for reactive derived state:
481
+ Use `useEffect` to sync derived state or perform side effects:
274
482
 
275
483
  ```jac
484
+ cl import from react { useState, useEffect }
485
+
276
486
  cl {
277
- let [todoState, setTodoState] = createState({
278
- "items": []
279
- });
487
+ def TodoApp() -> any {
488
+ let [todos, setTodos] = useState([]);
489
+ let [stats, setStats] = useState({
490
+ "total": 0,
491
+ "active": 0,
492
+ "completed": 0
493
+ });
280
494
 
281
- let [statsState, setStatsState] = createState({
282
- "total": 0,
283
- "active": 0,
284
- "completed": 0
285
- });
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;
286
500
 
287
- # Automatically update stats when todos change
288
- createEffect(lambda -> None {
289
- todos = todoState();
290
- total = len(todos.items);
291
- active = len([item for item in todos.items if not item.done]);
292
- completed = total - active;
293
-
294
- setStatsState({
295
- "total": total,
296
- "active": active,
297
- "completed": completed
298
- });
299
- });
501
+ setStats({
502
+ "total": total,
503
+ "active": active,
504
+ "completed": completed
505
+ });
300
506
 
301
- def TodoApp() -> any {
302
- todos = todoState();
303
- stats = statsState();
507
+ # Optional: Save to localStorage
508
+ localStorage.setItem("todoStats", JSON.stringify(stats));
509
+ }, [todos]);
304
510
 
305
511
  return <div>
306
512
  <StatsDisplay stats={stats} />
307
- {[TodoItem(item) for item in todos.items]}
513
+ {todos.map(lambda item: any -> any {
514
+ return <TodoItem key={item._jac_id} todo={item} />;
515
+ })}
308
516
  </div>;
309
517
  }
518
+
519
+ def app() -> any {
520
+ return <TodoApp />;
521
+ }
310
522
  }
311
523
  ```
312
524
 
313
525
  ---
314
526
 
315
- ## State Management Patterns
527
+ ## Advanced React Hooks
316
528
 
317
- ### Pattern 1: State Reducers
529
+ ### useReducer for Complex State
318
530
 
319
- Create reducer-like functions for complex state updates:
531
+ When state logic becomes complex, use `useReducer` instead of `useState`:
320
532
 
321
533
  ```jac
322
- cl {
323
- let [todoState, setTodoState] = createState({
324
- "items": [],
325
- "filter": "all",
326
- "input": ""
327
- });
328
-
329
- def todoReducer(action: str, payload: any) -> None {
330
- s = todoState();
534
+ cl import from react { useReducer, useEffect }
331
535
 
332
- if action == "ADD_TODO" {
333
- newItem = {
334
- "id": payload.id,
335
- "text": payload.text,
336
- "done": False
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
+ })
337
559
  };
338
- setTodoState({"items": s.items.concat([newItem])});
560
+ } elif type == "SET_FILTER" {
561
+ return {...state, "filter": action.payload};
562
+ } elif type == "SET_LOADING" {
563
+ return {...state, "loading": action.payload};
564
+ }
339
565
 
340
- } elif action == "TOGGLE_TODO" {
341
- updated = [item for item in s.items {
342
- if item.id == payload.id {
343
- return {"id": item.id, "text": item.text, "done": not item.done};
344
- }
345
- return item;
346
- }];
347
- setTodoState({"items": updated});
566
+ return state;
567
+ }
348
568
 
349
- } elif action == "REMOVE_TODO" {
350
- remaining = [item for item in s.items if item.id != payload.id];
351
- setTodoState({"items": remaining});
569
+ def TodoApp() -> any {
570
+ # Initial state
571
+ initialState = {
572
+ "todos": [],
573
+ "filter": "all",
574
+ "loading": False
575
+ };
352
576
 
353
- } elif action == "SET_FILTER" {
354
- setTodoState({"filter": payload.filter});
577
+ let [state, dispatch] = useReducer(todoReducer, initialState);
355
578
 
356
- } elif action == "CLEAR_COMPLETED" {
357
- remaining = [item for item in s.items if not item.done];
358
- setTodoState({"items": remaining});
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});
359
595
  }
360
- }
361
596
 
362
- async def addTodo(text: str) -> None {
363
- new_todo = await __jacSpawn("create_todo", {"text": text});
364
- todoReducer("ADD_TODO", {
365
- "id": new_todo._jac_id,
366
- "text": new_todo.text
367
- });
368
- }
597
+ def toggleTodo(id: str) -> None {
598
+ dispatch({"type": "TOGGLE_TODO", "payload": id});
599
+ }
369
600
 
370
- async def toggleTodo(id: str) -> None {
371
- await __jacSpawn("toggle_todo", {}, id);
372
- todoReducer("TOGGLE_TODO", {"id": id});
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>;
373
611
  }
374
612
 
375
- def setFilter(filter: str) -> None {
376
- todoReducer("SET_FILTER", {"filter": filter});
613
+ def app() -> any {
614
+ return <TodoApp />;
377
615
  }
378
616
  }
379
617
  ```
380
618
 
381
- ### Pattern 2: State Selectors
619
+ ### useContext for Global State
382
620
 
383
- Create selector functions to extract specific state slices:
621
+ Share state across multiple components without prop drilling:
384
622
 
385
623
  ```jac
624
+ cl import from react { useState, useContext, createContext }
625
+
386
626
  cl {
387
- let [todoState, setTodoState] = createState({
388
- "items": [],
389
- "filter": "all",
390
- "loading": False,
391
- "error": None
392
- });
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
+ }
393
650
 
394
- # Selectors
395
- def getTodos() -> list {
396
- return todoState().items;
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>;
397
662
  }
398
663
 
399
- def getActiveTodos() -> list {
400
- return [item for item in todoState().items if not item.done];
664
+ # Hook to use context
665
+ def useTodoContext() -> dict {
666
+ return useContext(TodoContext);
401
667
  }
402
668
 
403
- def getCompletedTodos() -> list {
404
- return [item for item in todoState().items if item.done];
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>;
405
688
  }
406
689
 
407
- def getFilteredTodos() -> list {
408
- s = todoState();
409
- if s.filter == "active" {
410
- return getActiveTodos();
411
- } elif s.filter == "completed" {
412
- return getCompletedTodos();
413
- }
414
- return getTodos();
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>;
415
698
  }
416
699
 
417
- def isLoading() -> bool {
418
- return todoState().loading;
700
+ # App with provider
701
+ def MainApp() -> any {
702
+ return <TodoProvider>
703
+ <FilterButtons />
704
+ <TodoList />
705
+ </TodoProvider>;
419
706
  }
420
707
 
421
- def getError() -> str | None {
422
- return todoState().error;
708
+ def app() -> any {
709
+ return <MainApp />;
423
710
  }
424
711
  }
425
712
  ```
426
713
 
427
- ### Pattern 3: State Actions
714
+ ### useCallback for Memoized Functions
428
715
 
429
- Create action functions that encapsulate state updates:
716
+ Prevent unnecessary re-renders by memoizing callbacks:
430
717
 
431
718
  ```jac
719
+ cl import from react { useState, useCallback }
720
+
432
721
  cl {
433
- let [todoState, setTodoState] = createState({
434
- "items": [],
435
- "filter": "all"
436
- });
722
+ def TodoApp() -> any {
723
+ let [todos, setTodos] = useState([]);
437
724
 
438
- # Action creators
439
- async def createTodoAction(text: str) -> None {
440
- if not text.trim() {
441
- return;
442
- }
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]);
443
734
 
444
- new_todo = await __jacSpawn("create_todo", {"text": text});
445
- s = todoState();
446
- newItem = {
447
- "id": new_todo._jac_id,
448
- "text": new_todo.text,
449
- "done": new_todo.done
450
- };
451
- setTodoState({"items": s.items.concat([newItem])});
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>;
452
744
  }
453
745
 
454
- async def toggleTodoAction(id: str) -> None {
455
- await __jacSpawn("toggle_todo", {}, id);
456
- s = todoState();
457
- updated = [item for item in s.items {
458
- if item.id == id {
459
- return {"id": item.id, "text": item.text, "done": not item.done};
460
- }
461
- return item;
462
- }];
463
- setTodoState({"items": updated});
746
+ def app() -> any {
747
+ return <TodoApp />;
464
748
  }
749
+ }
750
+ ```
465
751
 
466
- def removeTodoAction(id: str) -> None {
467
- s = todoState();
468
- remaining = [item for item in s.items if item.id != id];
469
- setTodoState({"items": remaining});
470
- }
752
+ ---
753
+
754
+ ## State Management Patterns
755
+
756
+ ### Pattern 1: Action Functions
471
757
 
472
- def setFilterAction(filter: str) -> None {
473
- setTodoState({"filter": filter});
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>;
474
801
  }
475
802
 
476
- def clearCompletedAction() -> None {
477
- s = todoState();
478
- remaining = [item for item in s.items if not item.done];
479
- setTodoState({"items": remaining});
803
+ def app() -> any {
804
+ return <TodoApp />;
480
805
  }
481
806
  }
482
807
  ```
483
808
 
484
- ---
485
-
486
- ## Complete Example: Multi-State Todo App
809
+ ### Pattern 2: Selector Functions with useMemo
487
810
 
488
- Here's a complete example combining multiple state patterns:
811
+ Create memoized selector functions for derived data:
489
812
 
490
813
  ```jac
814
+ cl import from react { useState, useMemo }
815
+
491
816
  cl {
492
- # User state
493
- let [userState, setUserState] = createState({
494
- "profile": None,
495
- "isLoading": False
496
- });
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]);
497
830
 
498
- # Todo state
499
- let [todoState, setTodoState] = createState({
500
- "items": [],
501
- "loading": False,
502
- "error": None
503
- });
831
+ activeTodos = useMemo(lambda -> list {
832
+ return todos.filter(lambda t: any -> bool { return not t.done; });
833
+ }, [todos]);
504
834
 
505
- # Filter state
506
- let [filterState, setFilterState] = createState({
507
- "filter": "all"
508
- });
835
+ completedTodos = useMemo(lambda -> list {
836
+ return todos.filter(lambda t: any -> bool { return t.done; });
837
+ }, [todos]);
509
838
 
510
- # UI state
511
- let [uiState, setUiState] = createState({
512
- "sidebarOpen": False,
513
- "modalOpen": False
514
- });
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
+ }
515
847
 
516
- # Derived state functions
517
- def getFilteredTodos() -> list {
518
- todos = todoState();
519
- filter = filterState();
848
+ def app() -> any {
849
+ return <TodoApp />;
850
+ }
851
+ }
852
+ ```
520
853
 
521
- if filter == "active" {
522
- return [item for item in todos.items if not item.done];
523
- } elif filter == "completed" {
524
- return [item for item in todos.items if item.done];
525
- }
526
- return todos.items;
527
- }
528
-
529
- def getStats() -> dict {
530
- todos = todoState();
531
- total = len(todos.items);
532
- active = len([item for item in todos.items if not item.done]);
533
- completed = total - active;
534
-
535
- return {"total": total, "active": active, "completed": completed};
536
- }
537
-
538
- # Actions
539
- async def loadTodos() -> None {
540
- setTodoState({"loading": True, "error": None});
541
- try {
542
- todos = await __jacSpawn("read_todos");
543
- items = [{
544
- "id": todo._jac_id,
545
- "text": todo.text,
546
- "done": todo.done
547
- } for todo in todos.reports];
548
- setTodoState({"items": items, "loading": False});
549
- } except Exception as err {
550
- setTodoState({"error": str(err), "loading": False});
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;
551
886
  }
552
- }
553
887
 
554
- async def addTodo(text: str) -> None {
555
- new_todo = await __jacSpawn("create_todo", {"text": text});
556
- s = todoState();
557
- newItem = {
558
- "id": new_todo._jac_id,
559
- "text": new_todo.text,
560
- "done": new_todo.done
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
561
908
  };
562
- setTodoState({"items": s.items.concat([newItem])});
563
- }
564
909
 
565
- def setFilter(filter: str) -> None {
566
- setFilterState({"filter": filter});
910
+ return <AppContext.Provider value={contextValue}>
911
+ <TodoList />
912
+ </AppContext.Provider>;
567
913
  }
568
914
 
569
- def toggleSidebar() -> None {
570
- s = uiState();
571
- setUiState({"sidebarOpen": not s.sidebarOpen});
915
+ def app() -> any {
916
+ return <App />;
572
917
  }
918
+ }
919
+ ```
573
920
 
574
- # Components
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 {
575
931
  def TodoApp() -> any {
576
- onMount(lambda -> None {
577
- loadTodos();
578
- });
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]);
579
1000
 
580
- todos = todoState();
581
- filter = filterState();
582
- ui = uiState();
1001
+ removeTodo = useCallback(lambda id: str -> None {
1002
+ setTodos(todos.filter(lambda t: any -> bool { return t._jac_id != id; }));
1003
+ }, [todos]);
583
1004
 
584
- if todos.loading {
585
- return <div>Loading...</div>;
586
- }
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]);
587
1012
 
588
- if todos.error {
589
- return <div>Error: {todos.error}</div>;
1013
+ # Render
1014
+ if loading {
1015
+ return <div style={{"padding": "20px"}}>Loading...</div>;
590
1016
  }
591
1017
 
592
- filtered = getFilteredTodos();
593
- stats = getStats();
1018
+ if error {
1019
+ return <div style={{"padding": "20px", "color": "red"}}>
1020
+ Error: {error}
1021
+ </div>;
1022
+ }
594
1023
 
595
- return <div>
596
- <Header stats={stats} onToggleSidebar={toggleSidebar} />
597
- {ui.sidebarOpen and <Sidebar filter={filter} onSetFilter={setFilter} />}
598
- <TodoList items={filtered} />
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>
599
1076
  </div>;
600
1077
  }
1078
+
1079
+ def app() -> any {
1080
+ return <TodoApp />;
1081
+ }
601
1082
  }
602
1083
  ```
603
1084
 
@@ -605,102 +1086,181 @@ cl {
605
1086
 
606
1087
  ## Best Practices
607
1088
 
608
- ### 1. Separate Concerns
1089
+ ### 1. Separate State Variables by Concern
609
1090
 
610
1091
  ```jac
611
- # Good: Separate state by concern
612
- let [userState, setUserState] = createState({"profile": None});
613
- let [todoState, setTodoState] = createState({"items": []});
614
- let [uiState, setUiState] = createState({"sidebarOpen": False});
615
-
616
- # Avoid: Mixing unrelated state
617
- let [appState, setAppState] = createState({
618
- "user": None,
619
- "todos": [],
620
- "sidebarOpen": False,
621
- "theme": "light"
622
- });
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
+ }
623
1111
  ```
624
1112
 
625
- ### 2. Keep State Flat
1113
+ ### 2. Use useMemo for Expensive Computations
626
1114
 
627
1115
  ```jac
628
- # Good: Flat state structure
629
- let [todoState, setTodoState] = createState({
630
- "items": [],
631
- "loading": False
632
- });
633
-
634
- # Avoid: Deeply nested state
635
- let [appState, setAppState] = createState({
636
- "data": {
637
- "todos": {
638
- "items": [],
639
- "meta": {
640
- "loading": False
641
- }
642
- }
643
- }
644
- });
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
+ }
645
1134
  ```
646
1135
 
647
- ### 3. Use Selectors for Computed Values
1136
+ ### 3. Don't Store Derived State
648
1137
 
649
1138
  ```jac
650
- # Good: Computed values via functions
651
- def getActiveTodos() -> list {
652
- return [item for item in todoState().items if not item.done];
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;
653
1145
  }
654
1146
 
655
- # ❌ Avoid: Storing computed values in state
656
- let [todoState, setTodoState] = createState({
657
- "items": [],
658
- "activeTodos": [] # Computed, shouldn't be in state
659
- });
1147
+ # ❌ Avoid: Storing derived values in state
1148
+ def TodoApp() -> any {
1149
+ let [todos, setTodos] = useState([]);
1150
+ let [activeCount, setActiveCount] = useState(0); # Redundant!
1151
+ }
660
1152
  ```
661
1153
 
662
- ### 4. Encapsulate State Updates
1154
+ ### 4. Use useReducer for Complex State Logic
663
1155
 
664
1156
  ```jac
665
- # Good: Action functions
666
- async def addTodo(text: str) -> None {
667
- new_todo = await __jacSpawn("create_todo", {"text": text});
668
- # Update state
669
- setTodoState(/* ... */);
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});
670
1169
  }
671
1170
 
672
- # ❌ Avoid: Direct state updates everywhere
673
- def Component() -> any {
674
- # Don't call setTodoState directly here
675
- # Use action functions instead
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
676
1176
  }
677
1177
  ```
678
1178
 
679
- ### 5. Handle Loading and Error States
1179
+ ### 5. Always Handle Loading and Error States
680
1180
 
681
1181
  ```jac
682
- # Good: Include loading/error in state
683
- let [todoState, setTodoState] = createState({
684
- "items": [],
685
- "loading": False,
686
- "error": None
687
- });
688
-
689
- # ❌ Avoid: Missing error handling
690
- let [todoState, setTodoState] = createState({
691
- "items": []
692
- });
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
+ }
693
1251
  ```
694
1252
 
695
1253
  ---
696
1254
 
697
1255
  ## Summary
698
1256
 
699
- - **Multiple States**: Split state by concern for better organization
700
- - **State Composition**: Combine multiple states for complex applications
701
- - **Derived State**: Compute values from multiple states using functions
702
- - **State Patterns**: Use reducers, selectors, and actions for complex state
703
- - **Best Practices**: Keep state flat, separate concerns, handle errors
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
704
1264
 
705
- Advanced state management helps you build scalable, maintainable applications! 🚀
1265
+ React hooks provide a powerful and flexible way to manage state in Jac applications! 🚀
706
1266