jac-client 0.2.3__py3-none-any.whl → 0.2.8__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 (224) hide show
  1. jac_client/examples/all-in-one/app.jac +494 -347
  2. jac_client/examples/all-in-one/assets/workers/worker.py +5 -0
  3. jac_client/examples/all-in-one/button.jac +1 -1
  4. jac_client/examples/all-in-one/components/CategoryFilter.jac +35 -0
  5. jac_client/examples/all-in-one/components/Header.jac +13 -0
  6. jac_client/examples/all-in-one/components/ProfitOverview.jac +50 -0
  7. jac_client/examples/all-in-one/components/Summary.jac +53 -0
  8. jac_client/examples/all-in-one/components/TransactionForm.jac +158 -0
  9. jac_client/examples/all-in-one/components/TransactionItem.jac +55 -0
  10. jac_client/examples/all-in-one/components/TransactionList.jac +37 -0
  11. jac_client/examples/all-in-one/components/button.jac +1 -1
  12. jac_client/examples/all-in-one/components/navigation.jac +132 -0
  13. jac_client/examples/all-in-one/constants/categories.jac +37 -0
  14. jac_client/examples/all-in-one/constants/clients.jac +13 -0
  15. jac_client/examples/all-in-one/context/BudgetContext.jac +28 -0
  16. jac_client/examples/all-in-one/hooks/useBudget.jac +116 -0
  17. jac_client/examples/all-in-one/hooks/useLocalStorage.jac +36 -0
  18. jac_client/examples/all-in-one/pages/BudgetPlanner.cl.jac +70 -0
  19. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +126 -0
  20. jac_client/examples/all-in-one/pages/FeaturesTest.cl.jac +552 -0
  21. jac_client/examples/all-in-one/pages/FeaturesTest.jac +126 -0
  22. jac_client/examples/all-in-one/pages/LandingPage.jac +101 -0
  23. jac_client/examples/all-in-one/pages/loginPage.jac +132 -0
  24. jac_client/examples/all-in-one/pages/nestedDemo.jac +61 -0
  25. jac_client/examples/all-in-one/pages/notFound.jac +24 -0
  26. jac_client/examples/all-in-one/pages/signupPage.jac +133 -0
  27. jac_client/examples/all-in-one/utils/formatters.jac +52 -0
  28. jac_client/examples/asset-serving/css-with-image/{app.jac → src/app.jac} +4 -4
  29. jac_client/examples/asset-serving/image-asset/{app.jac → src/app.jac} +4 -4
  30. jac_client/examples/asset-serving/import-alias/{app.jac → src/app.jac} +5 -5
  31. jac_client/examples/basic/{app.jac → src/app.jac} +4 -4
  32. jac_client/examples/basic-auth/src/app.jac +371 -0
  33. jac_client/examples/basic-auth-with-router/{app.jac → src/app.jac} +28 -28
  34. jac_client/examples/basic-full-stack/{app.jac → src/app.jac} +166 -127
  35. jac_client/examples/css-styling/js-styling/{app.jac → src/app.jac} +7 -7
  36. jac_client/examples/css-styling/material-ui/{app.jac → src/app.jac} +6 -6
  37. jac_client/examples/css-styling/pure-css/{app.jac → src/app.jac} +7 -7
  38. jac_client/examples/css-styling/sass-example/{app.jac → src/app.jac} +7 -7
  39. jac_client/examples/css-styling/styled-components/{app.jac → src/app.jac} +6 -6
  40. jac_client/examples/css-styling/tailwind-example/{app.jac → src/app.jac} +7 -7
  41. jac_client/examples/full-stack-with-auth/{app.jac → src/app.jac} +47 -47
  42. jac_client/examples/little-x/{app.jac → src/app.jac} +27 -32
  43. jac_client/examples/little-x/src/submit-button.jac +16 -0
  44. jac_client/examples/nested-folders/nested-advance/{ButtonRoot.jac → src/ButtonRoot.jac} +1 -1
  45. jac_client/examples/nested-folders/nested-advance/{app.jac → src/app.jac} +1 -1
  46. jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/ButtonSecondL.jac +1 -1
  47. jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/Card.jac +1 -1
  48. jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/level2/ButtonThirdL.jac +1 -1
  49. jac_client/examples/nested-folders/nested-basic/{app.jac → src/app.jac} +2 -2
  50. jac_client/examples/nested-folders/nested-basic/{button.jac → src/button.jac} +1 -1
  51. jac_client/examples/nested-folders/nested-basic/{components → src/components}/button.jac +1 -1
  52. jac_client/examples/ts-support/src/app.jac +35 -0
  53. jac_client/examples/with-router/{app.jac → src/app.jac} +15 -15
  54. jac_client/plugin/cli.jac +504 -0
  55. jac_client/plugin/client.jac +45 -0
  56. jac_client/plugin/client_runtime.cl.jac +42 -0
  57. jac_client/plugin/impl/client.impl.jac +193 -0
  58. jac_client/plugin/impl/client_runtime.impl.jac +195 -0
  59. jac_client/plugin/impl/vite_client_bundle.impl.jac +72 -0
  60. jac_client/plugin/plugin_config.jac +195 -0
  61. jac_client/plugin/src/__init__.jac +20 -0
  62. jac_client/plugin/src/asset_processor.jac +33 -0
  63. jac_client/plugin/src/babel_processor.jac +18 -0
  64. jac_client/plugin/src/compiler.jac +67 -0
  65. jac_client/plugin/src/config_loader.jac +32 -0
  66. jac_client/plugin/src/impl/asset_processor.impl.jac +127 -0
  67. jac_client/plugin/src/impl/babel_processor.impl.jac +89 -0
  68. jac_client/plugin/src/impl/compiler.impl.jac +288 -0
  69. jac_client/plugin/src/impl/config_loader.impl.jac +119 -0
  70. jac_client/plugin/src/impl/import_processor.impl.jac +33 -0
  71. jac_client/plugin/src/impl/jac_to_js.impl.jac +41 -0
  72. jac_client/plugin/src/impl/package_installer.impl.jac +105 -0
  73. jac_client/plugin/src/impl/vite_bundler.impl.jac +626 -0
  74. jac_client/plugin/src/import_processor.jac +19 -0
  75. jac_client/plugin/src/jac_to_js.jac +35 -0
  76. jac_client/plugin/src/package_installer.jac +26 -0
  77. jac_client/plugin/src/vite_bundler.jac +44 -0
  78. jac_client/plugin/vite_client_bundle.jac +31 -0
  79. jac_client/tests/conftest.py +283 -0
  80. jac_client/tests/fixtures/basic-app/app.jac +2 -2
  81. jac_client/tests/fixtures/cl_file/app.cl.jac +2 -2
  82. jac_client/tests/fixtures/client_app_with_antd/app.jac +1 -1
  83. jac_client/tests/fixtures/js_import/app.jac +5 -5
  84. jac_client/tests/fixtures/spawn_test/app.jac +15 -18
  85. jac_client/tests/fixtures/with-ts/app.jac +35 -0
  86. jac_client/tests/test_cli.py +811 -0
  87. jac_client/tests/test_it.py +592 -97
  88. {jac_client-0.2.3.dist-info → jac_client-0.2.8.dist-info}/METADATA +41 -34
  89. jac_client-0.2.8.dist-info/RECORD +97 -0
  90. {jac_client-0.2.3.dist-info → jac_client-0.2.8.dist-info}/WHEEL +2 -1
  91. jac_client-0.2.8.dist-info/entry_points.txt +4 -0
  92. jac_client-0.2.8.dist-info/top_level.txt +1 -0
  93. jac_client/docs/README.md +0 -689
  94. jac_client/docs/advanced-state.md +0 -1265
  95. jac_client/docs/asset-serving/intro.md +0 -209
  96. jac_client/docs/assets/pipe_line-v2.svg +0 -32
  97. jac_client/docs/assets/pipe_line.png +0 -0
  98. jac_client/docs/file-system/app.jac.md +0 -121
  99. jac_client/docs/file-system/backend-frontend.md +0 -217
  100. jac_client/docs/file-system/intro.md +0 -72
  101. jac_client/docs/file-system/nested-imports.md +0 -348
  102. jac_client/docs/guide-example/intro.md +0 -115
  103. jac_client/docs/guide-example/step-01-setup.md +0 -270
  104. jac_client/docs/guide-example/step-02-components.md +0 -416
  105. jac_client/docs/guide-example/step-03-styling.md +0 -478
  106. jac_client/docs/guide-example/step-04-todo-ui.md +0 -477
  107. jac_client/docs/guide-example/step-05-local-state.md +0 -530
  108. jac_client/docs/guide-example/step-06-events.md +0 -749
  109. jac_client/docs/guide-example/step-07-effects.md +0 -468
  110. jac_client/docs/guide-example/step-08-walkers.md +0 -534
  111. jac_client/docs/guide-example/step-09-authentication.md +0 -586
  112. jac_client/docs/guide-example/step-10-routing.md +0 -539
  113. jac_client/docs/guide-example/step-11-final.md +0 -963
  114. jac_client/docs/imports.md +0 -1141
  115. jac_client/docs/lifecycle-hooks.md +0 -773
  116. jac_client/docs/routing.md +0 -659
  117. jac_client/docs/styling/intro.md +0 -249
  118. jac_client/docs/styling/js-styling.md +0 -367
  119. jac_client/docs/styling/material-ui.md +0 -341
  120. jac_client/docs/styling/pure-css.md +0 -299
  121. jac_client/docs/styling/sass.md +0 -403
  122. jac_client/docs/styling/styled-components.md +0 -395
  123. jac_client/docs/styling/tailwind.md +0 -298
  124. jac_client/examples/all-in-one/.babelrc +0 -9
  125. jac_client/examples/all-in-one/README.md +0 -16
  126. jac_client/examples/all-in-one/assets/burger.png +0 -0
  127. jac_client/examples/all-in-one/package.json +0 -29
  128. jac_client/examples/all-in-one/styles.css +0 -26
  129. jac_client/examples/all-in-one/vite.config.js +0 -28
  130. jac_client/examples/asset-serving/css-with-image/.babelrc +0 -9
  131. jac_client/examples/asset-serving/css-with-image/README.md +0 -91
  132. jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
  133. jac_client/examples/asset-serving/css-with-image/package.json +0 -28
  134. jac_client/examples/asset-serving/css-with-image/styles.css +0 -26
  135. jac_client/examples/asset-serving/css-with-image/vite.config.js +0 -28
  136. jac_client/examples/asset-serving/image-asset/.babelrc +0 -9
  137. jac_client/examples/asset-serving/image-asset/README.md +0 -119
  138. jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
  139. jac_client/examples/asset-serving/image-asset/package.json +0 -28
  140. jac_client/examples/asset-serving/image-asset/styles.css +0 -26
  141. jac_client/examples/asset-serving/image-asset/vite.config.js +0 -28
  142. jac_client/examples/asset-serving/import-alias/.babelrc +0 -9
  143. jac_client/examples/asset-serving/import-alias/README.md +0 -83
  144. jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
  145. jac_client/examples/asset-serving/import-alias/package.json +0 -28
  146. jac_client/examples/asset-serving/import-alias/vite.config.js +0 -28
  147. jac_client/examples/basic/.babelrc +0 -9
  148. jac_client/examples/basic/README.md +0 -16
  149. jac_client/examples/basic/package.json +0 -27
  150. jac_client/examples/basic/vite.config.js +0 -27
  151. jac_client/examples/basic-auth/.babelrc +0 -9
  152. jac_client/examples/basic-auth/README.md +0 -16
  153. jac_client/examples/basic-auth/app.jac +0 -308
  154. jac_client/examples/basic-auth/package.json +0 -27
  155. jac_client/examples/basic-auth/vite.config.js +0 -27
  156. jac_client/examples/basic-auth-with-router/.babelrc +0 -9
  157. jac_client/examples/basic-auth-with-router/README.md +0 -60
  158. jac_client/examples/basic-auth-with-router/package.json +0 -28
  159. jac_client/examples/basic-auth-with-router/vite.config.js +0 -27
  160. jac_client/examples/basic-full-stack/.babelrc +0 -9
  161. jac_client/examples/basic-full-stack/README.md +0 -18
  162. jac_client/examples/basic-full-stack/package.json +0 -28
  163. jac_client/examples/basic-full-stack/vite.config.js +0 -27
  164. jac_client/examples/css-styling/js-styling/.babelrc +0 -9
  165. jac_client/examples/css-styling/js-styling/README.md +0 -183
  166. jac_client/examples/css-styling/js-styling/package.json +0 -28
  167. jac_client/examples/css-styling/js-styling/styles.js +0 -100
  168. jac_client/examples/css-styling/js-styling/vite.config.js +0 -27
  169. jac_client/examples/css-styling/material-ui/.babelrc +0 -9
  170. jac_client/examples/css-styling/material-ui/README.md +0 -16
  171. jac_client/examples/css-styling/material-ui/package.json +0 -32
  172. jac_client/examples/css-styling/material-ui/vite.config.js +0 -27
  173. jac_client/examples/css-styling/pure-css/.babelrc +0 -9
  174. jac_client/examples/css-styling/pure-css/README.md +0 -16
  175. jac_client/examples/css-styling/pure-css/package.json +0 -28
  176. jac_client/examples/css-styling/pure-css/styles.css +0 -111
  177. jac_client/examples/css-styling/pure-css/vite.config.js +0 -27
  178. jac_client/examples/css-styling/sass-example/.babelrc +0 -9
  179. jac_client/examples/css-styling/sass-example/README.md +0 -16
  180. jac_client/examples/css-styling/sass-example/package.json +0 -29
  181. jac_client/examples/css-styling/sass-example/styles.scss +0 -153
  182. jac_client/examples/css-styling/sass-example/vite.config.js +0 -27
  183. jac_client/examples/css-styling/styled-components/.babelrc +0 -9
  184. jac_client/examples/css-styling/styled-components/README.md +0 -16
  185. jac_client/examples/css-styling/styled-components/package.json +0 -29
  186. jac_client/examples/css-styling/styled-components/styled.js +0 -90
  187. jac_client/examples/css-styling/styled-components/vite.config.js +0 -27
  188. jac_client/examples/css-styling/tailwind-example/.babelrc +0 -9
  189. jac_client/examples/css-styling/tailwind-example/README.md +0 -16
  190. jac_client/examples/css-styling/tailwind-example/global.css +0 -1
  191. jac_client/examples/css-styling/tailwind-example/package.json +0 -30
  192. jac_client/examples/css-styling/tailwind-example/vite.config.js +0 -29
  193. jac_client/examples/full-stack-with-auth/.babelrc +0 -9
  194. jac_client/examples/full-stack-with-auth/README.md +0 -16
  195. jac_client/examples/full-stack-with-auth/package.json +0 -28
  196. jac_client/examples/full-stack-with-auth/vite.config.js +0 -29
  197. jac_client/examples/little-x/package.json +0 -23
  198. jac_client/examples/little-x/submit-button.jac +0 -8
  199. jac_client/examples/nested-folders/nested-advance/.babelrc +0 -9
  200. jac_client/examples/nested-folders/nested-advance/README.md +0 -77
  201. jac_client/examples/nested-folders/nested-advance/package.json +0 -29
  202. jac_client/examples/nested-folders/nested-advance/vite.config.js +0 -28
  203. jac_client/examples/nested-folders/nested-basic/.babelrc +0 -9
  204. jac_client/examples/nested-folders/nested-basic/README.md +0 -183
  205. jac_client/examples/nested-folders/nested-basic/app.js +0 -7
  206. jac_client/examples/nested-folders/nested-basic/package.json +0 -28
  207. jac_client/examples/nested-folders/nested-basic/vite.config.js +0 -27
  208. jac_client/examples/with-router/.babelrc +0 -9
  209. jac_client/examples/with-router/README.md +0 -17
  210. jac_client/examples/with-router/package.json +0 -28
  211. jac_client/examples/with-router/vite.config.js +0 -27
  212. jac_client/plugin/cli.py +0 -244
  213. jac_client/plugin/client.py +0 -152
  214. jac_client/plugin/client_runtime.jac +0 -234
  215. jac_client/plugin/vite_client_bundle.py +0 -503
  216. jac_client/tests/fixtures/js_import/utils.js +0 -21
  217. jac_client/tests/fixtures/package-lock.json +0 -329
  218. jac_client/tests/fixtures/package.json +0 -11
  219. jac_client/tests/test_asset_examples.py +0 -322
  220. jac_client/tests/test_cl.py +0 -530
  221. jac_client/tests/test_create_jac_app.py +0 -131
  222. jac_client/tests/test_nested_file.py +0 -374
  223. jac_client-0.2.3.dist-info/RECORD +0 -171
  224. jac_client-0.2.3.dist-info/entry_points.txt +0 -4
@@ -1,468 +0,0 @@
1
- # Step 7: Component Lifecycle with `useEffect`
2
-
3
- > ** Quick Tip:** Each step has two parts. **Part 1** shows you what to build. **Part 2** explains why it works. Want to just build? Skip all Part 2 sections!
4
-
5
- In this step, you'll learn about **useEffect** - a way to run code when your component loads or when data changes!
6
-
7
- ---
8
-
9
- ## Part 1: Building the App
10
-
11
- ### Step 7.1: Understanding the Problem
12
-
13
- Right now, your todos reset every time you refresh the page. We need to:
14
- 1. Load todos when the app starts
15
- 2. Save todos when they change
16
-
17
- We'll use `useEffect` to handle this!
18
-
19
- ### Step 7.2: Add useEffect Import
20
-
21
- First, import `useEffect`:
22
-
23
- ```jac
24
- cl import from react {useState, useEffect}
25
-
26
- cl {
27
- # ... your components
28
- }
29
- ```
30
-
31
- ### Step 7.3: Run Code When App Loads
32
-
33
- Let's log a message when the app starts:
34
-
35
- ```jac
36
- cl import from react {useState, useEffect}
37
-
38
- cl {
39
- # ... (keep all your components from step 6)
40
-
41
- def app() -> any {
42
- let [todos, setTodos] = useState([]);
43
- let [input, setInput] = useState("");
44
- let [filter, setFilter] = useState("all");
45
-
46
- # Run once when component mounts
47
- useEffect(lambda -> None {
48
- console.log("App loaded!");
49
- }, []);
50
-
51
- # ... rest of your code
52
- }
53
- }
54
- ```
55
-
56
- **Open browser console (F12) and refresh** - you'll see "App loaded!" printed once!
57
-
58
- ### Step 7.4: Save to localStorage
59
-
60
- Let's persist todos using localStorage:
61
-
62
- ```jac
63
- cl import from react {useState, useEffect}
64
-
65
- cl {
66
- # ... (keep all components)
67
-
68
- def app() -> any {
69
- let [todos, setTodos] = useState([]);
70
- let [input, setInput] = useState("");
71
- let [filter, setFilter] = useState("all");
72
-
73
- # Load todos from localStorage when app mounts
74
- useEffect(lambda -> None {
75
- let saved = localStorage.getItem("todos");
76
- if saved {
77
- let parsed = JSON.parse(saved);
78
- setTodos(parsed);
79
- }
80
- }, []);
81
-
82
- # Save todos to localStorage whenever they change
83
- useEffect(lambda -> None {
84
- localStorage.setItem("todos", JSON.stringify(todos));
85
- }, [todos]);
86
-
87
- # ... (keep all your functions: addTodo, toggleTodo, deleteTodo, getFilteredTodos)
88
-
89
- # ... (keep your return statement with all the UI)
90
- }
91
- }
92
- ```
93
-
94
- **Try it!** Add some todos, then refresh the page - your todos persist!
95
-
96
- ### Step 7.5: Add Loading State
97
-
98
- Let's add a loading indicator:
99
-
100
- ```jac
101
- cl import from react {useState, useEffect}
102
-
103
- cl {
104
- # ... (keep all components)
105
-
106
- def app() -> any {
107
- let [todos, setTodos] = useState([]);
108
- let [input, setInput] = useState("");
109
- let [filter, setFilter] = useState("all");
110
- let [loading, setLoading] = useState(true);
111
-
112
- # Load todos
113
- useEffect(lambda -> None {
114
- console.log("Loading todos...");
115
-
116
- # Simulate loading delay
117
- setTimeout(lambda -> None {
118
- let saved = localStorage.getItem("todos");
119
- if saved {
120
- let parsed = JSON.parse(saved);
121
- setTodos(parsed);
122
- }
123
- setLoading(false);
124
- }, 500);
125
- }, []);
126
-
127
- # Save todos whenever they change
128
- useEffect(lambda -> None {
129
- if not loading { # Don't save during initial load
130
- localStorage.setItem("todos", JSON.stringify(todos));
131
- }
132
- }, [todos]);
133
-
134
- # ... (keep all your functions)
135
-
136
- # Show loading state
137
- if loading {
138
- return <div style={{
139
- "display": "flex",
140
- "justifyContent": "center",
141
- "alignItems": "center",
142
- "height": "100vh"
143
- }}>
144
- <h2>Loading todos...</h2>
145
- </div>;
146
- }
147
-
148
- # ... (keep your normal UI return statement)
149
- }
150
- }
151
- ```
152
-
153
- **Try it!** You'll see a brief loading message before your todos appear!
154
-
155
- ---
156
-
157
- **⏭ Want to skip the theory?** Jump to [Step 8: Walkers](./step-08-walkers.md)
158
-
159
- ---
160
-
161
- ## Part 2: Understanding the Concepts
162
-
163
- ### What is `useEffect`?
164
-
165
- `useEffect` lets you run **side effects** - code that affects things outside your component.
166
-
167
- **Common side effects:**
168
- - Fetching data from a server
169
- - Saving data to localStorage
170
- - ⏰ Setting up timers
171
- - Logging analytics
172
- - Subscribing to events
173
-
174
- **Python analogy:**
175
-
176
- ```python
177
- # Python
178
- class TodoApp:
179
- def __init__(self):
180
- self.load_from_database() # Side effect: reads from DB
181
-
182
- # Jac
183
- def app() -> any {
184
- useEffect(lambda -> None {
185
- # Load data
186
- }, []);
187
- }
188
- ```
189
-
190
- ### useEffect Syntax
191
-
192
- ```jac
193
- useEffect(lambda -> None {
194
- # Code to run
195
- }, [dependencies]);
196
- ```
197
-
198
- **Two parameters:**
199
- 1. **Function** - What to run
200
- 2. **Dependencies** - When to re-run
201
-
202
- ### Dependency Array Controls When to Run
203
-
204
- **Run once (on mount):**
205
-
206
- ```jac
207
- useEffect(lambda -> None {
208
- console.log("Component mounted!");
209
- }, []); # Empty array = run once
210
- ```
211
-
212
- **Run when specific value changes:**
213
-
214
- ```jac
215
- useEffect(lambda -> None {
216
- console.log("Todos changed!");
217
- }, [todos]); # Run whenever todos changes
218
- ```
219
-
220
- **Run on every render (rarely used):**
221
-
222
- ```jac
223
- useEffect(lambda -> None {
224
- console.log("Component rendered!");
225
- }); # No array = run always (be careful!)
226
- ```
227
-
228
- ### Multiple useEffect Hooks
229
-
230
- You can use multiple `useEffect` hooks for different purposes:
231
-
232
- ```jac
233
- def app() -> any {
234
- let [todos, setTodos] = useState([]);
235
-
236
- # Effect 1: Load data once
237
- useEffect(lambda -> None {
238
- let saved = localStorage.getItem("todos");
239
- if saved {
240
- setTodos(JSON.parse(saved));
241
- }
242
- }, []);
243
-
244
- # Effect 2: Save when todos change
245
- useEffect(lambda -> None {
246
- localStorage.setItem("todos", JSON.stringify(todos));
247
- }, [todos]);
248
-
249
- # Effect 3: Log count changes
250
- useEffect(lambda -> None {
251
- console.log("Todo count:", todos.length);
252
- }, [todos.length]);
253
- }
254
- ```
255
-
256
- This keeps your code organized!
257
-
258
- ### useEffect Lifecycle
259
-
260
- ```
261
- 1. Component renders
262
-
263
- 2. UI updates on screen
264
-
265
- 3. useEffect runs
266
-
267
- 4. State changes (from effect)
268
-
269
- 5. Component re-renders
270
-
271
- 6. useEffect runs again (if dependencies changed)
272
- ```
273
-
274
- ### localStorage API
275
-
276
- Browser's built-in storage:
277
-
278
- ```jac
279
- # Save data
280
- localStorage.setItem("key", "value");
281
-
282
- # Load data
283
- let value = localStorage.getItem("key");
284
-
285
- # Remove data
286
- localStorage.removeItem("key");
287
-
288
- # Clear all
289
- localStorage.clear();
290
-
291
- # For objects/arrays, use JSON
292
- localStorage.setItem("todos", JSON.stringify(todos));
293
- let todos = JSON.parse(localStorage.getItem("todos"));
294
- ```
295
-
296
- **Storage limits:**
297
- - ~5-10 MB per domain
298
- - Persists across browser sessions
299
- - Only stores strings (use JSON for objects)
300
-
301
- ### Preventing Initial Save
302
-
303
- When loading from localStorage, you don't want to immediately save back:
304
-
305
- ```jac
306
- let [loading, setLoading] = useState(true);
307
-
308
- # Load
309
- useEffect(lambda -> None {
310
- # ... load data ...
311
- setLoading(false);
312
- }, []);
313
-
314
- # Save (skip during initial load)
315
- useEffect(lambda -> None {
316
- if not loading {
317
- localStorage.setItem("todos", JSON.stringify(todos));
318
- }
319
- }, [todos]);
320
- ```
321
-
322
- ### Common useEffect Patterns
323
-
324
- **Pattern 1: Fetch on mount**
325
-
326
- ```jac
327
- useEffect(lambda -> None {
328
- async def fetchData() -> None {
329
- let data = await apiCall();
330
- setState(data);
331
- }
332
- fetchData();
333
- }, []);
334
- ```
335
-
336
- **Pattern 2: Sync with external system**
337
-
338
- ```jac
339
- useEffect(lambda -> None {
340
- localStorage.setItem("key", value);
341
- }, [value]);
342
- ```
343
-
344
- **Pattern 3: Cleanup (timers, subscriptions)**
345
-
346
- ```jac
347
- useEffect(lambda -> None {
348
- let timerId = setInterval(lambda -> None {
349
- console.log("Tick");
350
- }, 1000);
351
-
352
- # Return cleanup function
353
- return lambda -> None {
354
- clearInterval(timerId);
355
- };
356
- }, []);
357
- ```
358
-
359
- **Pattern 4: Conditional effect**
360
-
361
- ```jac
362
- useEffect(lambda -> None {
363
- if someCondition {
364
- # Do something
365
- }
366
- }, [someCondition]);
367
- ```
368
-
369
- ---
370
-
371
- ## What You've Learned
372
-
373
- - What useEffect is and why we need it
374
- - How to run code when component mounts
375
- - Dependency arrays control when effects run
376
- - Multiple useEffect hooks for organization
377
- - Using localStorage to persist data
378
- - Adding loading states
379
- - Preventing unnecessary saves
380
-
381
- ---
382
-
383
- ## Common Issues
384
-
385
- ### Issue: Effect runs too many times
386
-
387
- **Check:** Is your dependency array correct?
388
-
389
- ```jac
390
- # Wrong - runs on every render
391
- useEffect(lambda -> None {
392
- console.log(todos);
393
- });
394
-
395
- # Correct - runs only when todos change
396
- useEffect(lambda -> None {
397
- console.log(todos);
398
- }, [todos]);
399
- ```
400
-
401
- ### Issue: Effect doesn't run when it should
402
-
403
- **Check:** Did you include all dependencies?
404
-
405
- ```jac
406
- # Wrong - missing todos dependency
407
- useEffect(lambda -> None {
408
- console.log(todos.length);
409
- }, []);
410
-
411
- # Correct - includes todos
412
- useEffect(lambda -> None {
413
- console.log(todos.length);
414
- }, [todos]);
415
- ```
416
-
417
- ### Issue: localStorage data not loading
418
-
419
- **Check:**
420
- - Are you parsing JSON? `JSON.parse(saved)`
421
- - Are you checking if data exists? `if saved { ... }`
422
- - Is the key name correct? `"todos"` in both save and load
423
-
424
- ### Issue: Infinite loop
425
-
426
- **Cause:** Effect updates state, which triggers effect again
427
-
428
- ```jac
429
- # Wrong - infinite loop!
430
- useEffect(lambda -> None {
431
- setTodos([...]); # This triggers effect again!
432
- }, [todos]);
433
-
434
- # Correct - run only once
435
- useEffect(lambda -> None {
436
- setTodos([...]);
437
- }, []);
438
- ```
439
-
440
- ---
441
-
442
- ## Quick Exercise
443
-
444
- Try adding a "last saved" timestamp:
445
-
446
- ```jac
447
- let [lastSaved, setLastSaved] = useState(None);
448
-
449
- useEffect(lambda -> None {
450
- if not loading and todos.length > 0 {
451
- localStorage.setItem("todos", JSON.stringify(todos));
452
- setLastSaved(Date().toLocaleTimeString());
453
- }
454
- }, [todos]);
455
-
456
- # Display it
457
- {(<p>Last saved: {lastSaved}</p>) if lastSaved else None}
458
- ```
459
-
460
- ---
461
-
462
- ## Next Step
463
-
464
- Great! Your app now persists data with localStorage. But localStorage is only local to your browser!
465
-
466
- In the next step, we'll add **real backend** using **walkers** so your data is stored on a server!
467
-
468
- **[Continue to Step 8: Walkers](./step-08-walkers.md)**