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.
- jac_client/examples/all-in-one/assets/workers/worker.py +5 -0
- jac_client/tests/conftest.py +281 -0
- jac_client/tests/test_cli.py +755 -0
- jac_client/tests/test_it.py +347 -67
- {jac_client-0.2.3.dist-info → jac_client-0.2.4.dist-info}/METADATA +28 -30
- jac_client-0.2.4.dist-info/RECORD +10 -0
- {jac_client-0.2.3.dist-info → jac_client-0.2.4.dist-info}/WHEEL +2 -1
- jac_client-0.2.4.dist-info/entry_points.txt +4 -0
- jac_client-0.2.4.dist-info/top_level.txt +1 -0
- jac_client/docs/README.md +0 -689
- jac_client/docs/advanced-state.md +0 -1265
- jac_client/docs/asset-serving/intro.md +0 -209
- jac_client/docs/assets/pipe_line-v2.svg +0 -32
- jac_client/docs/assets/pipe_line.png +0 -0
- jac_client/docs/file-system/app.jac.md +0 -121
- jac_client/docs/file-system/backend-frontend.md +0 -217
- jac_client/docs/file-system/intro.md +0 -72
- jac_client/docs/file-system/nested-imports.md +0 -348
- jac_client/docs/guide-example/intro.md +0 -115
- jac_client/docs/guide-example/step-01-setup.md +0 -270
- jac_client/docs/guide-example/step-02-components.md +0 -416
- jac_client/docs/guide-example/step-03-styling.md +0 -478
- jac_client/docs/guide-example/step-04-todo-ui.md +0 -477
- jac_client/docs/guide-example/step-05-local-state.md +0 -530
- jac_client/docs/guide-example/step-06-events.md +0 -749
- jac_client/docs/guide-example/step-07-effects.md +0 -468
- jac_client/docs/guide-example/step-08-walkers.md +0 -534
- jac_client/docs/guide-example/step-09-authentication.md +0 -586
- jac_client/docs/guide-example/step-10-routing.md +0 -539
- jac_client/docs/guide-example/step-11-final.md +0 -963
- jac_client/docs/imports.md +0 -1141
- jac_client/docs/lifecycle-hooks.md +0 -773
- jac_client/docs/routing.md +0 -659
- jac_client/docs/styling/intro.md +0 -249
- jac_client/docs/styling/js-styling.md +0 -367
- jac_client/docs/styling/material-ui.md +0 -341
- jac_client/docs/styling/pure-css.md +0 -299
- jac_client/docs/styling/sass.md +0 -403
- jac_client/docs/styling/styled-components.md +0 -395
- jac_client/docs/styling/tailwind.md +0 -298
- jac_client/examples/all-in-one/.babelrc +0 -9
- jac_client/examples/all-in-one/README.md +0 -16
- jac_client/examples/all-in-one/app.jac +0 -426
- jac_client/examples/all-in-one/assets/burger.png +0 -0
- jac_client/examples/all-in-one/button.jac +0 -7
- jac_client/examples/all-in-one/components/button.jac +0 -7
- jac_client/examples/all-in-one/package.json +0 -29
- jac_client/examples/all-in-one/styles.css +0 -26
- jac_client/examples/all-in-one/vite.config.js +0 -28
- jac_client/examples/asset-serving/css-with-image/.babelrc +0 -9
- jac_client/examples/asset-serving/css-with-image/README.md +0 -91
- jac_client/examples/asset-serving/css-with-image/app.jac +0 -88
- jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
- jac_client/examples/asset-serving/css-with-image/package.json +0 -28
- jac_client/examples/asset-serving/css-with-image/styles.css +0 -26
- jac_client/examples/asset-serving/css-with-image/vite.config.js +0 -28
- jac_client/examples/asset-serving/image-asset/.babelrc +0 -9
- jac_client/examples/asset-serving/image-asset/README.md +0 -119
- jac_client/examples/asset-serving/image-asset/app.jac +0 -55
- jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
- jac_client/examples/asset-serving/image-asset/package.json +0 -28
- jac_client/examples/asset-serving/image-asset/styles.css +0 -26
- jac_client/examples/asset-serving/image-asset/vite.config.js +0 -28
- jac_client/examples/asset-serving/import-alias/.babelrc +0 -9
- jac_client/examples/asset-serving/import-alias/README.md +0 -83
- jac_client/examples/asset-serving/import-alias/app.jac +0 -111
- jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
- jac_client/examples/asset-serving/import-alias/package.json +0 -28
- jac_client/examples/asset-serving/import-alias/vite.config.js +0 -28
- jac_client/examples/basic/.babelrc +0 -9
- jac_client/examples/basic/README.md +0 -16
- jac_client/examples/basic/app.jac +0 -21
- jac_client/examples/basic/package.json +0 -27
- jac_client/examples/basic/vite.config.js +0 -27
- jac_client/examples/basic-auth/.babelrc +0 -9
- jac_client/examples/basic-auth/README.md +0 -16
- jac_client/examples/basic-auth/app.jac +0 -308
- jac_client/examples/basic-auth/package.json +0 -27
- jac_client/examples/basic-auth/vite.config.js +0 -27
- jac_client/examples/basic-auth-with-router/.babelrc +0 -9
- jac_client/examples/basic-auth-with-router/README.md +0 -60
- jac_client/examples/basic-auth-with-router/app.jac +0 -464
- jac_client/examples/basic-auth-with-router/package.json +0 -28
- jac_client/examples/basic-auth-with-router/vite.config.js +0 -27
- jac_client/examples/basic-full-stack/.babelrc +0 -9
- jac_client/examples/basic-full-stack/README.md +0 -18
- jac_client/examples/basic-full-stack/app.jac +0 -320
- jac_client/examples/basic-full-stack/package.json +0 -28
- jac_client/examples/basic-full-stack/vite.config.js +0 -27
- jac_client/examples/css-styling/js-styling/.babelrc +0 -9
- jac_client/examples/css-styling/js-styling/README.md +0 -183
- jac_client/examples/css-styling/js-styling/app.jac +0 -84
- jac_client/examples/css-styling/js-styling/package.json +0 -28
- jac_client/examples/css-styling/js-styling/styles.js +0 -100
- jac_client/examples/css-styling/js-styling/vite.config.js +0 -27
- jac_client/examples/css-styling/material-ui/.babelrc +0 -9
- jac_client/examples/css-styling/material-ui/README.md +0 -16
- jac_client/examples/css-styling/material-ui/app.jac +0 -122
- jac_client/examples/css-styling/material-ui/package.json +0 -32
- jac_client/examples/css-styling/material-ui/vite.config.js +0 -27
- jac_client/examples/css-styling/pure-css/.babelrc +0 -9
- jac_client/examples/css-styling/pure-css/README.md +0 -16
- jac_client/examples/css-styling/pure-css/app.jac +0 -64
- jac_client/examples/css-styling/pure-css/package.json +0 -28
- jac_client/examples/css-styling/pure-css/styles.css +0 -111
- jac_client/examples/css-styling/pure-css/vite.config.js +0 -27
- jac_client/examples/css-styling/sass-example/.babelrc +0 -9
- jac_client/examples/css-styling/sass-example/README.md +0 -16
- jac_client/examples/css-styling/sass-example/app.jac +0 -64
- jac_client/examples/css-styling/sass-example/package.json +0 -29
- jac_client/examples/css-styling/sass-example/styles.scss +0 -153
- jac_client/examples/css-styling/sass-example/vite.config.js +0 -27
- jac_client/examples/css-styling/styled-components/.babelrc +0 -9
- jac_client/examples/css-styling/styled-components/README.md +0 -16
- jac_client/examples/css-styling/styled-components/app.jac +0 -71
- jac_client/examples/css-styling/styled-components/package.json +0 -29
- jac_client/examples/css-styling/styled-components/styled.js +0 -90
- jac_client/examples/css-styling/styled-components/vite.config.js +0 -27
- jac_client/examples/css-styling/tailwind-example/.babelrc +0 -9
- jac_client/examples/css-styling/tailwind-example/README.md +0 -16
- jac_client/examples/css-styling/tailwind-example/app.jac +0 -63
- jac_client/examples/css-styling/tailwind-example/global.css +0 -1
- jac_client/examples/css-styling/tailwind-example/package.json +0 -30
- jac_client/examples/css-styling/tailwind-example/vite.config.js +0 -29
- jac_client/examples/full-stack-with-auth/.babelrc +0 -9
- jac_client/examples/full-stack-with-auth/README.md +0 -16
- jac_client/examples/full-stack-with-auth/app.jac +0 -722
- jac_client/examples/full-stack-with-auth/package.json +0 -28
- jac_client/examples/full-stack-with-auth/vite.config.js +0 -29
- jac_client/examples/little-x/app.jac +0 -724
- jac_client/examples/little-x/package.json +0 -23
- jac_client/examples/little-x/submit-button.jac +0 -8
- jac_client/examples/nested-folders/nested-advance/.babelrc +0 -9
- jac_client/examples/nested-folders/nested-advance/ButtonRoot.jac +0 -11
- jac_client/examples/nested-folders/nested-advance/README.md +0 -77
- jac_client/examples/nested-folders/nested-advance/app.jac +0 -35
- jac_client/examples/nested-folders/nested-advance/level1/ButtonSecondL.jac +0 -19
- jac_client/examples/nested-folders/nested-advance/level1/Card.jac +0 -43
- jac_client/examples/nested-folders/nested-advance/level1/level2/ButtonThirdL.jac +0 -25
- jac_client/examples/nested-folders/nested-advance/package.json +0 -29
- jac_client/examples/nested-folders/nested-advance/vite.config.js +0 -28
- jac_client/examples/nested-folders/nested-basic/.babelrc +0 -9
- jac_client/examples/nested-folders/nested-basic/README.md +0 -183
- jac_client/examples/nested-folders/nested-basic/app.jac +0 -13
- jac_client/examples/nested-folders/nested-basic/app.js +0 -7
- jac_client/examples/nested-folders/nested-basic/button.jac +0 -7
- jac_client/examples/nested-folders/nested-basic/components/button.jac +0 -7
- jac_client/examples/nested-folders/nested-basic/package.json +0 -28
- jac_client/examples/nested-folders/nested-basic/vite.config.js +0 -27
- jac_client/examples/with-router/.babelrc +0 -9
- jac_client/examples/with-router/README.md +0 -17
- jac_client/examples/with-router/app.jac +0 -323
- jac_client/examples/with-router/package.json +0 -28
- jac_client/examples/with-router/vite.config.js +0 -27
- jac_client/plugin/cli.py +0 -244
- jac_client/plugin/client.py +0 -152
- jac_client/plugin/client_runtime.jac +0 -234
- jac_client/plugin/vite_client_bundle.py +0 -503
- jac_client/tests/fixtures/basic-app/app.jac +0 -23
- jac_client/tests/fixtures/cl_file/app.cl.jac +0 -48
- jac_client/tests/fixtures/cl_file/app.jac +0 -15
- jac_client/tests/fixtures/client_app_with_antd/app.jac +0 -34
- jac_client/tests/fixtures/js_import/app.jac +0 -34
- jac_client/tests/fixtures/js_import/utils.js +0 -21
- jac_client/tests/fixtures/package-lock.json +0 -329
- jac_client/tests/fixtures/package.json +0 -11
- jac_client/tests/fixtures/relative_import/app.jac +0 -11
- jac_client/tests/fixtures/relative_import/button.jac +0 -7
- jac_client/tests/fixtures/spawn_test/app.jac +0 -129
- jac_client/tests/fixtures/test_fragments_spread/app.jac +0 -67
- jac_client/tests/test_asset_examples.py +0 -322
- jac_client/tests/test_cl.py +0 -530
- jac_client/tests/test_create_jac_app.py +0 -131
- jac_client/tests/test_nested_file.py +0 -374
- jac_client-0.2.3.dist-info/RECORD +0 -171
- 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)**
|