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.
- jac_client/docs/README.md +232 -172
- jac_client/docs/advanced-state.md +1012 -452
- jac_client/docs/asset-serving/intro.md +209 -0
- jac_client/docs/assets/pipe_line-v2.svg +32 -0
- jac_client/docs/assets/pipe_line.png +0 -0
- jac_client/docs/file-system/intro.md +90 -0
- jac_client/docs/guide-example/intro.md +117 -0
- jac_client/docs/guide-example/step-01-setup.md +260 -0
- jac_client/docs/guide-example/step-02-components.md +416 -0
- jac_client/docs/guide-example/step-03-styling.md +478 -0
- jac_client/docs/guide-example/step-04-todo-ui.md +477 -0
- jac_client/docs/guide-example/step-05-local-state.md +530 -0
- jac_client/docs/guide-example/step-06-events.md +750 -0
- jac_client/docs/guide-example/step-07-effects.md +469 -0
- jac_client/docs/guide-example/step-08-walkers.md +534 -0
- jac_client/docs/guide-example/step-09-authentication.md +586 -0
- jac_client/docs/guide-example/step-10-routing.md +540 -0
- jac_client/docs/guide-example/step-11-final.md +964 -0
- jac_client/docs/imports.md +538 -46
- jac_client/docs/lifecycle-hooks.md +517 -297
- jac_client/docs/routing.md +487 -357
- jac_client/docs/styling/intro.md +250 -0
- jac_client/docs/styling/js-styling.md +373 -0
- jac_client/docs/styling/material-ui.md +346 -0
- jac_client/docs/styling/pure-css.md +305 -0
- jac_client/docs/styling/sass.md +409 -0
- jac_client/docs/styling/styled-components.md +401 -0
- jac_client/docs/styling/tailwind.md +303 -0
- jac_client/examples/asset-serving/css-with-image/.babelrc +9 -0
- jac_client/examples/asset-serving/css-with-image/README.md +91 -0
- jac_client/examples/asset-serving/css-with-image/app.jac +67 -0
- jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
- jac_client/examples/asset-serving/css-with-image/package.json +28 -0
- jac_client/examples/asset-serving/css-with-image/styles.css +27 -0
- jac_client/examples/asset-serving/css-with-image/vite.config.js +29 -0
- jac_client/examples/asset-serving/image-asset/.babelrc +9 -0
- jac_client/examples/asset-serving/image-asset/README.md +119 -0
- jac_client/examples/asset-serving/image-asset/app.jac +43 -0
- jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
- jac_client/examples/asset-serving/image-asset/package.json +28 -0
- jac_client/examples/asset-serving/image-asset/styles.css +27 -0
- jac_client/examples/asset-serving/image-asset/vite.config.js +29 -0
- jac_client/examples/asset-serving/import-alias/.babelrc +9 -0
- jac_client/examples/asset-serving/import-alias/README.md +83 -0
- jac_client/examples/asset-serving/import-alias/app.jac +57 -0
- jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
- jac_client/examples/asset-serving/import-alias/package.json +28 -0
- jac_client/examples/asset-serving/import-alias/vite.config.js +29 -0
- jac_client/examples/basic/.babelrc +9 -0
- jac_client/examples/basic/README.md +16 -0
- jac_client/examples/basic/app.jac +16 -0
- jac_client/examples/basic/package.json +27 -0
- jac_client/examples/basic/vite.config.js +28 -0
- jac_client/examples/basic-auth/.babelrc +9 -0
- jac_client/examples/basic-auth/README.md +16 -0
- jac_client/examples/basic-auth/app.jac +308 -0
- jac_client/examples/basic-auth/package.json +27 -0
- jac_client/examples/basic-auth/vite.config.js +28 -0
- jac_client/examples/basic-auth-with-router/.babelrc +9 -0
- jac_client/examples/basic-auth-with-router/README.md +60 -0
- jac_client/examples/basic-auth-with-router/app.jac +464 -0
- jac_client/examples/basic-auth-with-router/package.json +28 -0
- jac_client/examples/basic-auth-with-router/vite.config.js +28 -0
- jac_client/examples/basic-full-stack/.babelrc +9 -0
- jac_client/examples/basic-full-stack/README.md +18 -0
- jac_client/examples/basic-full-stack/app.jac +320 -0
- jac_client/examples/basic-full-stack/package.json +28 -0
- jac_client/examples/basic-full-stack/vite.config.js +28 -0
- jac_client/examples/css-styling/js-styling/.babelrc +9 -0
- jac_client/examples/css-styling/js-styling/README.md +183 -0
- jac_client/examples/css-styling/js-styling/app.jac +63 -0
- jac_client/examples/css-styling/js-styling/package.json +28 -0
- jac_client/examples/css-styling/js-styling/styles.js +100 -0
- jac_client/examples/css-styling/js-styling/vite.config.js +28 -0
- jac_client/examples/css-styling/material-ui/.babelrc +9 -0
- jac_client/examples/css-styling/material-ui/README.md +16 -0
- jac_client/examples/css-styling/material-ui/app.jac +82 -0
- jac_client/examples/css-styling/material-ui/package.json +32 -0
- jac_client/examples/css-styling/material-ui/vite.config.js +28 -0
- jac_client/examples/css-styling/pure-css/.babelrc +9 -0
- jac_client/examples/css-styling/pure-css/README.md +16 -0
- jac_client/examples/css-styling/pure-css/app.jac +63 -0
- jac_client/examples/css-styling/pure-css/package.json +28 -0
- jac_client/examples/css-styling/pure-css/styles.css +112 -0
- jac_client/examples/css-styling/pure-css/vite.config.js +28 -0
- jac_client/examples/css-styling/sass-example/.babelrc +9 -0
- jac_client/examples/css-styling/sass-example/README.md +16 -0
- jac_client/examples/css-styling/sass-example/app.jac +63 -0
- jac_client/examples/css-styling/sass-example/package.json +29 -0
- jac_client/examples/css-styling/sass-example/styles.scss +158 -0
- jac_client/examples/css-styling/sass-example/vite.config.js +28 -0
- jac_client/examples/css-styling/styled-components/.babelrc +9 -0
- jac_client/examples/css-styling/styled-components/README.md +16 -0
- jac_client/examples/css-styling/styled-components/app.jac +66 -0
- jac_client/examples/css-styling/styled-components/package.json +29 -0
- jac_client/examples/css-styling/styled-components/styled.js +91 -0
- jac_client/examples/css-styling/styled-components/vite.config.js +28 -0
- jac_client/examples/css-styling/tailwind-example/.babelrc +9 -0
- jac_client/examples/css-styling/tailwind-example/README.md +16 -0
- jac_client/examples/css-styling/tailwind-example/app.jac +64 -0
- jac_client/examples/css-styling/tailwind-example/global.css +1 -0
- jac_client/examples/css-styling/tailwind-example/package.json +30 -0
- jac_client/examples/css-styling/tailwind-example/vite.config.js +30 -0
- jac_client/examples/full-stack-with-auth/.babelrc +9 -0
- jac_client/examples/full-stack-with-auth/README.md +16 -0
- jac_client/examples/full-stack-with-auth/app.jac +735 -0
- jac_client/examples/full-stack-with-auth/package.json +28 -0
- jac_client/examples/full-stack-with-auth/vite.config.js +30 -0
- jac_client/examples/with-router/.babelrc +9 -0
- jac_client/examples/with-router/README.md +17 -0
- jac_client/examples/with-router/app.jac +323 -0
- jac_client/examples/with-router/package.json +28 -0
- jac_client/examples/with-router/vite.config.js +28 -0
- jac_client/plugin/cli.py +95 -179
- jac_client/plugin/client.py +111 -2
- jac_client/plugin/client_runtime.jac +183 -890
- jac_client/plugin/vite_client_bundle.py +185 -205
- jac_client/tests/__init__.py +0 -1
- jac_client/tests/fixtures/{client_app.jac → basic-app/app.jac} +1 -1
- jac_client/tests/fixtures/cl_file/app.cl.jac +38 -0
- jac_client/tests/fixtures/cl_file/app.jac +15 -0
- jac_client/tests/fixtures/{client_app_with_antd.jac → client_app_with_antd/app.jac} +7 -0
- jac_client/tests/fixtures/{js_import.jac → js_import/app.jac} +2 -2
- jac_client/tests/fixtures/{relative_import.jac → relative_import/app.jac} +1 -1
- jac_client/tests/fixtures/{button.jac → relative_import/button.jac} +2 -2
- jac_client/tests/fixtures/spawn_test/app.jac +133 -0
- jac_client/tests/fixtures/{test_fragments_spread.jac → test_fragments_spread/app.jac} +11 -2
- jac_client/tests/test_asset_examples.py +339 -0
- jac_client/tests/test_cl.py +345 -151
- jac_client/tests/test_create_jac_app.py +41 -45
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/METADATA +72 -16
- jac_client-0.2.1.dist-info/RECORD +140 -0
- jac_client/examples/little-x/package-lock.json +0 -2840
- jac_client/examples/todo-app/README.md +0 -82
- jac_client/examples/todo-app/app.jac +0 -683
- jac_client/examples/todo-app/package-lock.json +0 -999
- jac_client/examples/todo-app/package.json +0 -22
- jac_client-0.1.0.dist-info/RECORD +0 -33
- /jac_client/tests/fixtures/{utils.js → js_import/utils.js} +0 -0
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/WHEEL +0 -0
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
# Lifecycle Hooks in Jac: Component Lifecycle Management
|
|
2
2
|
|
|
3
|
-
Learn how to use `
|
|
3
|
+
Learn how to use React's `useEffect` and `useState` hooks to manage component state, initialization, side effects, and cleanup.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## 📚 Table of Contents
|
|
8
8
|
|
|
9
9
|
- [What are Lifecycle Hooks?](#what-are-lifecycle-hooks)
|
|
10
|
-
- [
|
|
10
|
+
- [React Hooks (Recommended)](#react-hooks-recommended)
|
|
11
|
+
- [useState](#usestate)
|
|
12
|
+
- [useEffect](#useeffect)
|
|
11
13
|
- [Common Use Cases](#common-use-cases)
|
|
12
14
|
- [Complete Examples](#complete-examples)
|
|
13
15
|
- [Best Practices](#best-practices)
|
|
16
|
+
- [Legacy Jac Hooks](#legacy-jac-hooks)
|
|
14
17
|
|
|
15
18
|
---
|
|
16
19
|
|
|
@@ -21,94 +24,192 @@ Lifecycle hooks are functions that let you run code at specific points in a comp
|
|
|
21
24
|
- **When component updates**: React to state changes
|
|
22
25
|
- **When component unmounts**: Clean up resources
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
**Jac uses React hooks as the standard approach:**
|
|
28
|
+
- **useState**: Manage component state
|
|
29
|
+
- **useEffect**: Handle side effects, lifecycle events, and cleanup
|
|
25
30
|
|
|
26
31
|
**Key Benefits:**
|
|
27
32
|
- **Initialization**: Load data when component appears
|
|
28
33
|
- **Side Effects**: Set up subscriptions, timers, or listeners
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
34
|
+
- **Reactive Updates**: Run code when specific dependencies change
|
|
35
|
+
- **Cleanup**: Properly clean up resources when components unmount
|
|
36
|
+
- **Standard React API**: Works exactly like React hooks you already know
|
|
31
37
|
|
|
32
38
|
---
|
|
33
39
|
|
|
34
|
-
##
|
|
40
|
+
## React Hooks (Recommended)
|
|
35
41
|
|
|
36
|
-
###
|
|
42
|
+
### useState
|
|
43
|
+
|
|
44
|
+
The `useState` hook lets you add state to your components.
|
|
45
|
+
|
|
46
|
+
#### Basic Usage
|
|
47
|
+
|
|
48
|
+
```jac
|
|
49
|
+
cl import from react { useState }
|
|
50
|
+
|
|
51
|
+
cl {
|
|
52
|
+
def Counter() -> any {
|
|
53
|
+
let [count, setCount] = useState(0);
|
|
54
|
+
|
|
55
|
+
return <div>
|
|
56
|
+
<h1>Count: {count}</h1>
|
|
57
|
+
<button onClick={lambda e: any -> None {
|
|
58
|
+
setCount(count + 1);
|
|
59
|
+
}}>
|
|
60
|
+
Increment
|
|
61
|
+
</button>
|
|
62
|
+
</div>;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Key Points:**
|
|
68
|
+
- Import `useState` from `react`
|
|
69
|
+
- Returns an array: `[currentValue, setterFunction]`
|
|
70
|
+
- Use destructuring to get the value and setter
|
|
71
|
+
- Call the setter function to update state
|
|
72
|
+
|
|
73
|
+
#### Multiple State Variables
|
|
37
74
|
|
|
38
75
|
```jac
|
|
76
|
+
cl import from react { useState }
|
|
77
|
+
|
|
78
|
+
cl {
|
|
79
|
+
def TodoApp() -> any {
|
|
80
|
+
let [todos, setTodos] = useState([]);
|
|
81
|
+
let [inputValue, setInputValue] = useState("");
|
|
82
|
+
let [filter, setFilter] = useState("all");
|
|
83
|
+
|
|
84
|
+
return <div>Todo App</div>;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### useEffect
|
|
90
|
+
|
|
91
|
+
The `useEffect` hook lets you perform side effects in your components. It provides full lifecycle management including mount, update, and cleanup.
|
|
92
|
+
|
|
93
|
+
#### Basic Usage - Run on Mount
|
|
94
|
+
|
|
95
|
+
```jac
|
|
96
|
+
cl import from react { useState, useEffect }
|
|
97
|
+
|
|
39
98
|
cl {
|
|
40
99
|
def MyComponent() -> any {
|
|
41
|
-
|
|
100
|
+
let [data, setData] = useState(None);
|
|
101
|
+
|
|
102
|
+
useEffect(lambda -> None {
|
|
42
103
|
console.log("Component mounted!");
|
|
43
104
|
# Load initial data
|
|
105
|
+
async def loadData() -> None {
|
|
106
|
+
result = await jacSpawn("get_data", "", {});
|
|
107
|
+
setData(result);
|
|
108
|
+
}
|
|
44
109
|
loadData();
|
|
45
|
-
});
|
|
110
|
+
}, []); # Empty array means run only on mount
|
|
46
111
|
|
|
47
112
|
return <div>My Component</div>;
|
|
48
113
|
}
|
|
49
114
|
}
|
|
50
115
|
```
|
|
51
116
|
|
|
52
|
-
**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
117
|
+
**Key Points:**
|
|
118
|
+
- Import `useEffect` from `react`
|
|
119
|
+
- First argument: function to run
|
|
120
|
+
- Second argument: dependency array
|
|
121
|
+
- `[]` - run only on mount
|
|
122
|
+
- `[count]` - run when `count` changes
|
|
123
|
+
- No array - run on every render
|
|
57
124
|
|
|
58
|
-
|
|
125
|
+
#### useEffect with Dependencies
|
|
59
126
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- **Component Scoped**: Automatically tracks which component called it
|
|
63
|
-
- **Async Support**: Can contain async operations
|
|
127
|
+
```jac
|
|
128
|
+
cl import from react { useState, useEffect }
|
|
64
129
|
|
|
65
|
-
|
|
130
|
+
cl {
|
|
131
|
+
def Counter() -> any {
|
|
132
|
+
let [count, setCount] = useState(0);
|
|
66
133
|
|
|
67
|
-
|
|
134
|
+
useEffect(lambda -> None {
|
|
135
|
+
console.log("Count changed to:", count);
|
|
136
|
+
document.title = "Count: " + str(count);
|
|
137
|
+
}, [count]); # Run when count changes
|
|
68
138
|
|
|
69
|
-
|
|
139
|
+
return <div>
|
|
140
|
+
<h1>Count: {count}</h1>
|
|
141
|
+
<button onClick={lambda e: any -> None {
|
|
142
|
+
setCount(count + 1);
|
|
143
|
+
}}>
|
|
144
|
+
Increment
|
|
145
|
+
</button>
|
|
146
|
+
</div>;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
70
150
|
|
|
71
|
-
|
|
151
|
+
#### useEffect with Cleanup
|
|
72
152
|
|
|
73
153
|
```jac
|
|
154
|
+
cl import from react { useEffect }
|
|
155
|
+
|
|
74
156
|
cl {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
157
|
+
def TimerComponent() -> any {
|
|
158
|
+
useEffect(lambda -> any {
|
|
159
|
+
# Setup
|
|
160
|
+
intervalId = setInterval(lambda -> None {
|
|
161
|
+
console.log("Timer tick");
|
|
162
|
+
}, 1000);
|
|
163
|
+
|
|
164
|
+
# Cleanup function (returned from useEffect)
|
|
165
|
+
return lambda -> None {
|
|
166
|
+
clearInterval(intervalId);
|
|
167
|
+
};
|
|
168
|
+
}, []);
|
|
169
|
+
|
|
170
|
+
return <div>Timer Component</div>;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
79
174
|
|
|
80
|
-
|
|
81
|
-
setTodoState({"loading": True});
|
|
175
|
+
---
|
|
82
176
|
|
|
83
|
-
|
|
84
|
-
todos = await __jacSpawn("read_todos");
|
|
177
|
+
## Common Use Cases
|
|
85
178
|
|
|
86
|
-
|
|
87
|
-
for todo in todos.reports {
|
|
88
|
-
items.push({
|
|
89
|
-
"id": todo._jac_id,
|
|
90
|
-
"text": todo.text,
|
|
91
|
-
"done": todo.done
|
|
92
|
-
});
|
|
93
|
-
}
|
|
179
|
+
### 1. Loading Initial Data
|
|
94
180
|
|
|
95
|
-
|
|
96
|
-
|
|
181
|
+
The most common use case is loading data when a component mounts:
|
|
182
|
+
|
|
183
|
+
```jac
|
|
184
|
+
cl import from react { useState, useEffect }
|
|
185
|
+
cl import from '@jac-client/utils' { jacSpawn }
|
|
97
186
|
|
|
187
|
+
cl {
|
|
98
188
|
def TodoApp() -> any {
|
|
99
|
-
|
|
100
|
-
|
|
189
|
+
let [todos, setTodos] = useState([]);
|
|
190
|
+
let [loading, setLoading] = useState(True);
|
|
191
|
+
|
|
192
|
+
useEffect(lambda -> None {
|
|
193
|
+
async def loadTodos() -> None {
|
|
194
|
+
setLoading(True);
|
|
195
|
+
|
|
196
|
+
# Fetch todos from backend
|
|
197
|
+
result = await jacSpawn("read_todos", "", {});
|
|
198
|
+
console.log(result);
|
|
199
|
+
setTodos(result.reports);
|
|
200
|
+
setLoading(False);
|
|
201
|
+
}
|
|
101
202
|
loadTodos();
|
|
102
|
-
});
|
|
203
|
+
}, []); # Empty array = run only on mount
|
|
103
204
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if s.loading {
|
|
205
|
+
if loading {
|
|
107
206
|
return <div>Loading...</div>;
|
|
108
207
|
}
|
|
109
208
|
|
|
110
209
|
return <div>
|
|
111
|
-
{
|
|
210
|
+
{todos.map(lambda todo: any -> any {
|
|
211
|
+
return <TodoItem todo={todo} />;
|
|
212
|
+
})}
|
|
112
213
|
</div>;
|
|
113
214
|
}
|
|
114
215
|
}
|
|
@@ -116,39 +217,37 @@ cl {
|
|
|
116
217
|
|
|
117
218
|
### 2. Setting Up Event Listeners
|
|
118
219
|
|
|
119
|
-
Set up event listeners
|
|
220
|
+
Set up event listeners with proper cleanup:
|
|
120
221
|
|
|
121
222
|
```jac
|
|
223
|
+
cl import from react { useState, useEffect }
|
|
224
|
+
|
|
122
225
|
cl {
|
|
123
226
|
def WindowResizeHandler() -> any {
|
|
124
|
-
let [
|
|
125
|
-
|
|
126
|
-
"height": 0
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
def handleResize() -> None {
|
|
130
|
-
setWindowSize({
|
|
131
|
-
"width": window.innerWidth,
|
|
132
|
-
"height": window.innerHeight
|
|
133
|
-
});
|
|
134
|
-
}
|
|
227
|
+
let [width, setWidth] = useState(0);
|
|
228
|
+
let [height, setHeight] = useState(0);
|
|
135
229
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
230
|
+
useEffect(lambda -> any {
|
|
231
|
+
def handleResize() -> None {
|
|
232
|
+
setWidth(window.innerWidth);
|
|
233
|
+
setHeight(window.innerHeight);
|
|
234
|
+
}
|
|
140
235
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
});
|
|
236
|
+
# Set initial size
|
|
237
|
+
handleResize();
|
|
144
238
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
239
|
+
# Add listener
|
|
240
|
+
window.addEventListener("resize", handleResize);
|
|
241
|
+
|
|
242
|
+
# Cleanup function
|
|
243
|
+
return lambda -> None {
|
|
244
|
+
window.removeEventListener("resize", handleResize);
|
|
245
|
+
};
|
|
246
|
+
}, []);
|
|
150
247
|
|
|
151
|
-
return
|
|
248
|
+
return <div>
|
|
249
|
+
Window size: {width} x {height}
|
|
250
|
+
</div>;
|
|
152
251
|
}
|
|
153
252
|
}
|
|
154
253
|
```
|
|
@@ -158,45 +257,40 @@ cl {
|
|
|
158
257
|
Load user-specific data when a component mounts:
|
|
159
258
|
|
|
160
259
|
```jac
|
|
161
|
-
cl {
|
|
162
|
-
|
|
163
|
-
"profile": None,
|
|
164
|
-
"loading": True
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
async def loadUserProfile() -> None {
|
|
168
|
-
if not jacIsLoggedIn() {
|
|
169
|
-
navigate("/login");
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
# Fetch user profile
|
|
174
|
-
profile = await __jacSpawn("get_user_profile");
|
|
175
|
-
|
|
176
|
-
setUserState({
|
|
177
|
-
"profile": profile,
|
|
178
|
-
"loading": False
|
|
179
|
-
});
|
|
180
|
-
}
|
|
260
|
+
cl import from react { useState, useEffect }
|
|
261
|
+
cl import from '@jac-client/utils' { jacSpawn }
|
|
181
262
|
|
|
263
|
+
cl {
|
|
182
264
|
def ProfileView() -> any {
|
|
183
|
-
|
|
265
|
+
let [profile, setProfile] = useState(None);
|
|
266
|
+
let [loading, setLoading] = useState(True);
|
|
267
|
+
|
|
268
|
+
useEffect(lambda -> None {
|
|
269
|
+
async def loadUserProfile() -> None {
|
|
270
|
+
if not jacIsLoggedIn() {
|
|
271
|
+
navigate("/login");
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
# Fetch user profile
|
|
276
|
+
result = await jacSpawn("get_user_profile", "", {});
|
|
277
|
+
setProfile(result);
|
|
278
|
+
setLoading(False);
|
|
279
|
+
}
|
|
184
280
|
loadUserProfile();
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
s = userState();
|
|
281
|
+
}, []);
|
|
188
282
|
|
|
189
|
-
if
|
|
283
|
+
if loading {
|
|
190
284
|
return <div>Loading profile...</div>;
|
|
191
285
|
}
|
|
192
286
|
|
|
193
|
-
if not
|
|
287
|
+
if not profile {
|
|
194
288
|
return <div>No profile found</div>;
|
|
195
289
|
}
|
|
196
290
|
|
|
197
291
|
return <div>
|
|
198
|
-
<h1>{
|
|
199
|
-
<p>{
|
|
292
|
+
<h1>{profile.username}</h1>
|
|
293
|
+
<p>{profile.email}</p>
|
|
200
294
|
</div>;
|
|
201
295
|
}
|
|
202
296
|
}
|
|
@@ -207,16 +301,23 @@ cl {
|
|
|
207
301
|
Initialize external libraries or APIs:
|
|
208
302
|
|
|
209
303
|
```jac
|
|
304
|
+
cl import from react { useEffect }
|
|
305
|
+
|
|
210
306
|
cl {
|
|
211
307
|
def ChartComponent() -> any {
|
|
212
|
-
|
|
308
|
+
useEffect(lambda -> any {
|
|
213
309
|
# Initialize chart library
|
|
214
310
|
chart = new Chart("myChart", {
|
|
215
311
|
"type": "line",
|
|
216
312
|
"data": chartData,
|
|
217
313
|
"options": chartOptions
|
|
218
314
|
});
|
|
219
|
-
|
|
315
|
+
|
|
316
|
+
# Cleanup function
|
|
317
|
+
return lambda -> None {
|
|
318
|
+
chart.destroy();
|
|
319
|
+
};
|
|
320
|
+
}, []);
|
|
220
321
|
|
|
221
322
|
return <canvas id="myChart"></canvas>;
|
|
222
323
|
}
|
|
@@ -228,15 +329,17 @@ cl {
|
|
|
228
329
|
Focus an input field when a component mounts:
|
|
229
330
|
|
|
230
331
|
```jac
|
|
332
|
+
cl import from react { useEffect }
|
|
333
|
+
|
|
231
334
|
cl {
|
|
232
335
|
def SearchBar() -> any {
|
|
233
|
-
|
|
336
|
+
useEffect(lambda -> None {
|
|
234
337
|
# Focus search input on mount
|
|
235
338
|
inputEl = document.getElementById("search-input");
|
|
236
339
|
if inputEl {
|
|
237
340
|
inputEl.focus();
|
|
238
341
|
}
|
|
239
|
-
});
|
|
342
|
+
}, []);
|
|
240
343
|
|
|
241
344
|
return <input
|
|
242
345
|
id="search-input"
|
|
@@ -254,65 +357,112 @@ cl {
|
|
|
254
357
|
### Example 1: Todo App with Data Loading
|
|
255
358
|
|
|
256
359
|
```jac
|
|
257
|
-
cl {
|
|
258
|
-
|
|
259
|
-
"items": [],
|
|
260
|
-
"filter": "all",
|
|
261
|
-
"loading": False
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
async def read_todos_action() -> None {
|
|
265
|
-
setTodoState({"loading": True});
|
|
360
|
+
cl import from react { useState, useEffect }
|
|
361
|
+
cl import from '@jac-client/utils' { jacSpawn }
|
|
266
362
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
363
|
+
cl {
|
|
364
|
+
def app() -> any {
|
|
365
|
+
let [todos, setTodos] = useState([]);
|
|
366
|
+
let [inputValue, setInputValue] = useState("");
|
|
367
|
+
let [filter, setFilter] = useState("all");
|
|
368
|
+
|
|
369
|
+
useEffect(lambda -> None {
|
|
370
|
+
async def loadTodos() -> None {
|
|
371
|
+
todos = await jacSpawn("read_todos","",{});
|
|
372
|
+
console.log(todos);
|
|
373
|
+
setTodos(todos.reports);
|
|
277
374
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
375
|
+
loadTodos();
|
|
376
|
+
}, []);
|
|
377
|
+
|
|
378
|
+
# Add a new todo
|
|
379
|
+
async def addTodo() -> None {
|
|
380
|
+
if not inputValue.trim() { return; }
|
|
381
|
+
newTodo = {
|
|
382
|
+
"id": Date.now(),
|
|
383
|
+
"text": inputValue.trim(),
|
|
384
|
+
"done": False
|
|
385
|
+
};
|
|
386
|
+
await jacSpawn("create_todo","", {"text": inputValue.trim()});
|
|
387
|
+
newTodos = todos.concat([newTodo]);
|
|
388
|
+
setTodos(newTodos);
|
|
389
|
+
setInputValue("");
|
|
283
390
|
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
def TodoApp() -> any {
|
|
287
|
-
# Load todos when component mounts
|
|
288
|
-
onMount(lambda -> None {
|
|
289
|
-
read_todos_action();
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
s = todoState();
|
|
293
391
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
392
|
+
# Toggle todo completion status
|
|
393
|
+
async def toggleTodo(id: any) -> None {
|
|
394
|
+
await jacSpawn("toggle_todo",id, {});
|
|
395
|
+
setTodos(todos.map(lambda todo: any -> any {
|
|
396
|
+
if todo._jac_id == id {
|
|
397
|
+
updatedTodo = {
|
|
398
|
+
"_jac_id": todo._jac_id,
|
|
399
|
+
"text": todo.text,
|
|
400
|
+
"done": not todo.done,
|
|
401
|
+
"id": todo.id
|
|
402
|
+
};
|
|
403
|
+
return updatedTodo;
|
|
404
|
+
}
|
|
405
|
+
return todo;
|
|
406
|
+
}));
|
|
301
407
|
}
|
|
302
408
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
409
|
+
# Filter todos based on current filter
|
|
410
|
+
def getFilteredTodos() -> list {
|
|
411
|
+
if filter == "active" {
|
|
412
|
+
return todos.filter(lambda todo: any -> bool { return not todo.done; });
|
|
413
|
+
} elif filter == "completed" {
|
|
414
|
+
return todos.filter(lambda todo: any -> bool { return todo.done; });
|
|
415
|
+
}
|
|
416
|
+
return todos;
|
|
307
417
|
}
|
|
308
418
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
419
|
+
filteredTodos = getFilteredTodos();
|
|
420
|
+
|
|
421
|
+
return <div style={{
|
|
422
|
+
"maxWidth": "600px",
|
|
423
|
+
"margin": "40px auto",
|
|
424
|
+
"padding": "24px",
|
|
425
|
+
"fontFamily": "system-ui, -apple-system, sans-serif"
|
|
426
|
+
}}>
|
|
427
|
+
<h1 style={{"textAlign": "center"}}>📝 My Todo App</h1>
|
|
428
|
+
|
|
429
|
+
# Add todo form
|
|
430
|
+
<div style={{"display": "flex", "gap": "8px", "marginBottom": "24px"}}>
|
|
431
|
+
<input
|
|
432
|
+
type="text"
|
|
433
|
+
value={inputValue}
|
|
434
|
+
onChange={lambda e: any -> None { setInputValue(e.target.value); }}
|
|
435
|
+
onKeyPress={lambda e: any -> None {
|
|
436
|
+
if e.key == "Enter" { addTodo(); }
|
|
437
|
+
}}
|
|
438
|
+
placeholder="What needs to be done?"
|
|
439
|
+
style={{"flex": "1", "padding": "12px"}}
|
|
440
|
+
/>
|
|
441
|
+
<button onClick={addTodo} style={{"padding": "12px 24px"}}>
|
|
442
|
+
Add
|
|
443
|
+
</button>
|
|
444
|
+
</div>
|
|
445
|
+
|
|
446
|
+
# Filter buttons
|
|
447
|
+
<div style={{"display": "flex", "gap": "8px", "marginBottom": "16px"}}>
|
|
448
|
+
<button onClick={lambda -> None { setFilter("all"); }}>All</button>
|
|
449
|
+
<button onClick={lambda -> None { setFilter("active"); }}>Active</button>
|
|
450
|
+
<button onClick={lambda -> None { setFilter("completed"); }}>Completed</button>
|
|
451
|
+
</div>
|
|
452
|
+
|
|
453
|
+
# Todo list
|
|
454
|
+
<ul>
|
|
455
|
+
{filteredTodos.map(lambda todo: any -> any {
|
|
456
|
+
return <li key={todo._jac_id}>
|
|
457
|
+
<input
|
|
458
|
+
type="checkbox"
|
|
459
|
+
checked={todo.done}
|
|
460
|
+
onChange={lambda -> None { toggleTodo(todo._jac_id); }}
|
|
461
|
+
/>
|
|
462
|
+
<span>{todo.text}</span>
|
|
463
|
+
</li>;
|
|
464
|
+
})}
|
|
465
|
+
</ul>
|
|
316
466
|
</div>;
|
|
317
467
|
}
|
|
318
468
|
}
|
|
@@ -321,71 +471,68 @@ cl {
|
|
|
321
471
|
### Example 2: Dashboard with Multiple Data Sources
|
|
322
472
|
|
|
323
473
|
```jac
|
|
324
|
-
cl {
|
|
325
|
-
|
|
326
|
-
"stats": None,
|
|
327
|
-
"recentActivity": [],
|
|
328
|
-
"loading": True
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
async def loadDashboardData() -> None {
|
|
332
|
-
setDashboardState({"loading": True});
|
|
333
|
-
|
|
334
|
-
# Load multiple data sources in parallel
|
|
335
|
-
[stats, activity] = await Promise.all([
|
|
336
|
-
__jacSpawn("get_stats"),
|
|
337
|
-
__jacSpawn("get_recent_activity")
|
|
338
|
-
]);
|
|
339
|
-
|
|
340
|
-
setDashboardState({
|
|
341
|
-
"stats": stats,
|
|
342
|
-
"recentActivity": activity.reports,
|
|
343
|
-
"loading": False
|
|
344
|
-
});
|
|
345
|
-
}
|
|
474
|
+
cl import from react { useState, useEffect }
|
|
475
|
+
cl import from '@jac-client/utils' { jacSpawn }
|
|
346
476
|
|
|
477
|
+
cl {
|
|
347
478
|
def Dashboard() -> any {
|
|
348
|
-
|
|
349
|
-
|
|
479
|
+
let [stats, setStats] = useState(None);
|
|
480
|
+
let [activity, setActivity] = useState([]);
|
|
481
|
+
let [loading, setLoading] = useState(True);
|
|
482
|
+
|
|
483
|
+
useEffect(lambda -> None {
|
|
484
|
+
async def loadDashboardData() -> None {
|
|
485
|
+
setLoading(True);
|
|
486
|
+
|
|
487
|
+
# Load multiple data sources in parallel
|
|
488
|
+
results = await Promise.all([
|
|
489
|
+
jacSpawn("get_stats", "", {}),
|
|
490
|
+
jacSpawn("get_recent_activity", "", {})
|
|
491
|
+
]);
|
|
492
|
+
|
|
493
|
+
setStats(results[0]);
|
|
494
|
+
setActivity(results[1].reports);
|
|
495
|
+
setLoading(False);
|
|
496
|
+
}
|
|
350
497
|
loadDashboardData();
|
|
351
|
-
});
|
|
498
|
+
}, []);
|
|
352
499
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if s.loading {
|
|
500
|
+
if loading {
|
|
356
501
|
return <div>Loading dashboard...</div>;
|
|
357
502
|
}
|
|
358
503
|
|
|
359
504
|
return <div>
|
|
360
|
-
<StatsView stats={
|
|
361
|
-
<ActivityList activities={
|
|
505
|
+
<StatsView stats={stats} />
|
|
506
|
+
<ActivityList activities={activity} />
|
|
362
507
|
</div>;
|
|
363
508
|
}
|
|
364
509
|
}
|
|
365
510
|
```
|
|
366
511
|
|
|
367
|
-
### Example 3: Component with Cleanup
|
|
512
|
+
### Example 3: Timer Component with Cleanup
|
|
368
513
|
|
|
369
|
-
|
|
514
|
+
Proper cleanup when component unmounts:
|
|
370
515
|
|
|
371
516
|
```jac
|
|
372
|
-
cl {
|
|
373
|
-
let cleanupFunctions = [];
|
|
517
|
+
cl import from react { useState, useEffect }
|
|
374
518
|
|
|
519
|
+
cl {
|
|
375
520
|
def TimerComponent() -> any {
|
|
376
|
-
|
|
521
|
+
let [seconds, setSeconds] = useState(0);
|
|
522
|
+
|
|
523
|
+
useEffect(lambda -> any {
|
|
377
524
|
# Set up timer
|
|
378
525
|
intervalId = setInterval(lambda -> None {
|
|
379
|
-
|
|
526
|
+
setSeconds(lambda prev: int -> int { return prev + 1; });
|
|
380
527
|
}, 1000);
|
|
381
528
|
|
|
382
|
-
#
|
|
383
|
-
|
|
529
|
+
# Cleanup function - runs when component unmounts
|
|
530
|
+
return lambda -> None {
|
|
384
531
|
clearInterval(intervalId);
|
|
385
|
-
}
|
|
386
|
-
});
|
|
532
|
+
};
|
|
533
|
+
}, []);
|
|
387
534
|
|
|
388
|
-
return <div>Timer
|
|
535
|
+
return <div>Timer: {seconds} seconds</div>;
|
|
389
536
|
}
|
|
390
537
|
}
|
|
391
538
|
```
|
|
@@ -394,161 +541,234 @@ cl {
|
|
|
394
541
|
|
|
395
542
|
## Best Practices
|
|
396
543
|
|
|
397
|
-
### 1.
|
|
544
|
+
### 1. Always Specify Dependencies
|
|
545
|
+
|
|
546
|
+
Be explicit about what your effect depends on:
|
|
398
547
|
|
|
399
548
|
```jac
|
|
400
|
-
# ✅ Good:
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
549
|
+
# ✅ Good: Empty array for mount-only effects
|
|
550
|
+
useEffect(lambda -> None {
|
|
551
|
+
loadInitialData();
|
|
552
|
+
}, []);
|
|
553
|
+
|
|
554
|
+
# ✅ Good: Specify dependencies
|
|
555
|
+
useEffect(lambda -> None {
|
|
556
|
+
console.log("Count changed:", count);
|
|
557
|
+
}, [count]);
|
|
558
|
+
|
|
559
|
+
# ⚠️ Warning: No dependency array runs on every render
|
|
560
|
+
useEffect(lambda -> None {
|
|
561
|
+
console.log("Runs on every render!");
|
|
562
|
+
});
|
|
563
|
+
```
|
|
407
564
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
565
|
+
### 2. Handle Async Operations Properly
|
|
566
|
+
|
|
567
|
+
Always handle async operations with proper error handling:
|
|
568
|
+
|
|
569
|
+
```jac
|
|
570
|
+
# ✅ Good: Proper async handling
|
|
571
|
+
useEffect(lambda -> None {
|
|
572
|
+
async def loadData() -> None {
|
|
573
|
+
try {
|
|
574
|
+
data = await jacSpawn("get_data", "", {});
|
|
575
|
+
setData(data);
|
|
576
|
+
} except Exception as err {
|
|
577
|
+
console.error("Error loading data:", err);
|
|
578
|
+
setError(err);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
loadData();
|
|
582
|
+
}, []);
|
|
417
583
|
```
|
|
418
584
|
|
|
419
|
-
###
|
|
585
|
+
### 3. Clean Up Side Effects
|
|
420
586
|
|
|
421
|
-
Always
|
|
587
|
+
Always clean up event listeners, timers, and subscriptions:
|
|
422
588
|
|
|
423
589
|
```jac
|
|
424
|
-
# ✅ Good:
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
setState({"data": data});
|
|
429
|
-
} except Exception as err {
|
|
430
|
-
console.error("Error loading data:", err);
|
|
590
|
+
# ✅ Good: Cleanup function removes event listener
|
|
591
|
+
useEffect(lambda -> any {
|
|
592
|
+
def handleResize() -> None {
|
|
593
|
+
setWidth(window.innerWidth);
|
|
431
594
|
}
|
|
432
|
-
}
|
|
433
595
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
596
|
+
window.addEventListener("resize", handleResize);
|
|
597
|
+
|
|
598
|
+
return lambda -> None {
|
|
599
|
+
window.removeEventListener("resize", handleResize);
|
|
600
|
+
};
|
|
601
|
+
}, []);
|
|
440
602
|
```
|
|
441
603
|
|
|
442
|
-
###
|
|
604
|
+
### 4. Use Loading States
|
|
443
605
|
|
|
444
|
-
|
|
606
|
+
Show loading indicators while data is being fetched:
|
|
445
607
|
|
|
446
608
|
```jac
|
|
447
|
-
# ✅ Good:
|
|
448
|
-
def
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
609
|
+
# ✅ Good: Clear loading states
|
|
610
|
+
def Component() -> any {
|
|
611
|
+
let [data, setData] = useState(None);
|
|
612
|
+
let [loading, setLoading] = useState(True);
|
|
613
|
+
let [error, setError] = useState(None);
|
|
614
|
+
|
|
615
|
+
useEffect(lambda -> None {
|
|
616
|
+
async def loadData() -> None {
|
|
617
|
+
try {
|
|
618
|
+
setLoading(True);
|
|
619
|
+
result = await jacSpawn("get_data", "", {});
|
|
620
|
+
setData(result);
|
|
621
|
+
} except Exception as err {
|
|
622
|
+
setError(err);
|
|
623
|
+
} finally {
|
|
624
|
+
setLoading(False);
|
|
625
|
+
}
|
|
453
626
|
}
|
|
454
|
-
|
|
455
|
-
});
|
|
456
|
-
|
|
627
|
+
loadData();
|
|
628
|
+
}, []);
|
|
629
|
+
|
|
630
|
+
if loading { return <div>Loading...</div>; }
|
|
631
|
+
if error { return <div>Error: {error}</div>; }
|
|
632
|
+
return <div>{data}</div>;
|
|
457
633
|
}
|
|
458
634
|
```
|
|
459
635
|
|
|
460
|
-
###
|
|
636
|
+
### 5. Keep Effects Focused
|
|
461
637
|
|
|
462
|
-
|
|
638
|
+
Each effect should have a single responsibility:
|
|
463
639
|
|
|
464
640
|
```jac
|
|
641
|
+
# ✅ Good: Separate effects for separate concerns
|
|
465
642
|
def Component() -> any {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
643
|
+
useEffect(lambda -> None {
|
|
644
|
+
loadData(); # Data loading
|
|
645
|
+
}, []);
|
|
646
|
+
|
|
647
|
+
useEffect(lambda -> any {
|
|
648
|
+
# Event listener setup
|
|
649
|
+
window.addEventListener("resize", handleResize);
|
|
650
|
+
return lambda -> None {
|
|
651
|
+
window.removeEventListener("resize", handleResize);
|
|
652
|
+
};
|
|
653
|
+
}, []);
|
|
470
654
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
655
|
+
return <div>Component</div>;
|
|
656
|
+
}
|
|
657
|
+
```
|
|
474
658
|
|
|
475
|
-
|
|
476
|
-
if s.loading {
|
|
477
|
-
return <div>Loading...</div>;
|
|
478
|
-
}
|
|
659
|
+
### 6. Avoid Stale Closures
|
|
479
660
|
|
|
480
|
-
|
|
481
|
-
|
|
661
|
+
Be careful with closures capturing old state values:
|
|
662
|
+
|
|
663
|
+
```jac
|
|
664
|
+
# ❌ Avoid: Stale closure problem
|
|
665
|
+
useEffect(lambda -> None {
|
|
666
|
+
setInterval(lambda -> None {
|
|
667
|
+
setCount(count + 1); # count is stale!
|
|
668
|
+
}, 1000);
|
|
669
|
+
}, []);
|
|
670
|
+
|
|
671
|
+
# ✅ Good: Use functional update
|
|
672
|
+
useEffect(lambda -> any {
|
|
673
|
+
intervalId = setInterval(lambda -> None {
|
|
674
|
+
setCount(lambda prev: int -> int { return prev + 1; });
|
|
675
|
+
}, 1000);
|
|
676
|
+
|
|
677
|
+
return lambda -> None {
|
|
678
|
+
clearInterval(intervalId);
|
|
679
|
+
};
|
|
680
|
+
}, []);
|
|
482
681
|
```
|
|
483
682
|
|
|
484
|
-
|
|
683
|
+
---
|
|
684
|
+
|
|
685
|
+
## Summary
|
|
485
686
|
|
|
486
|
-
|
|
687
|
+
- **useState**: Manage component state (replaces `createState()`, `createSignal()`)
|
|
688
|
+
- **useEffect**: Handle side effects and lifecycle events (replaces `onMount()`, `createEffect()`)
|
|
689
|
+
- **Dependencies**: Always specify what your effect depends on
|
|
690
|
+
- **Cleanup**: Return a cleanup function for subscriptions, timers, and listeners
|
|
691
|
+
- **Best Practices**: Handle errors, use loading states, keep effects focused
|
|
692
|
+
|
|
693
|
+
React hooks provide a powerful and standard way to manage component lifecycle! 🎯
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## Legacy Jac Hooks
|
|
698
|
+
|
|
699
|
+
> **Note**: The following hooks are from older Jac versions. New projects should use React hooks instead.
|
|
700
|
+
|
|
701
|
+
### `onMount()` - Legacy
|
|
702
|
+
|
|
703
|
+
The `onMount()` hook was a Jac-specific hook for running code once when a component mounts:
|
|
487
704
|
|
|
488
705
|
```jac
|
|
489
|
-
#
|
|
706
|
+
# Legacy approach - use useEffect instead
|
|
490
707
|
def Component() -> any {
|
|
491
708
|
onMount(lambda -> None {
|
|
492
709
|
loadData();
|
|
493
|
-
setupEventListeners();
|
|
494
|
-
initializeThirdParty();
|
|
495
710
|
});
|
|
496
711
|
return <div>Component</div>;
|
|
497
712
|
}
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
**Modern equivalent:**
|
|
498
716
|
|
|
499
|
-
|
|
717
|
+
```jac
|
|
718
|
+
# Modern approach with React hooks
|
|
500
719
|
def Component() -> any {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
720
|
+
useEffect(lambda -> None {
|
|
721
|
+
loadData();
|
|
722
|
+
}, []);
|
|
504
723
|
return <div>Component</div>;
|
|
505
724
|
}
|
|
506
725
|
```
|
|
507
726
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
## When NOT to Use `onMount()`
|
|
727
|
+
### `createState()` - Legacy
|
|
511
728
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
If you need code to run when state changes, use `createEffect()` instead:
|
|
729
|
+
The `createState()` hook was a Jac-specific state management solution:
|
|
515
730
|
|
|
516
731
|
```jac
|
|
517
|
-
#
|
|
732
|
+
# Legacy approach - use useState instead
|
|
733
|
+
let [state, setState] = createState({"count": 0});
|
|
734
|
+
|
|
518
735
|
def Component() -> any {
|
|
519
|
-
|
|
736
|
+
s = state();
|
|
737
|
+
return <div>{s.count}</div>;
|
|
738
|
+
}
|
|
739
|
+
```
|
|
520
740
|
|
|
521
|
-
|
|
522
|
-
console.log("Count changed:", count());
|
|
523
|
-
});
|
|
741
|
+
**Modern equivalent:**
|
|
524
742
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
</div>;
|
|
743
|
+
```jac
|
|
744
|
+
# Modern approach with React hooks
|
|
745
|
+
def Component() -> any {
|
|
746
|
+
let [count, setCount] = useState(0);
|
|
747
|
+
return <div>{count}</div>;
|
|
530
748
|
}
|
|
749
|
+
```
|
|
531
750
|
|
|
532
|
-
|
|
533
|
-
def Component() -> any {
|
|
534
|
-
let [count, setCount] = createSignal(0);
|
|
751
|
+
### `createSignal()` and `createEffect()` - Legacy
|
|
535
752
|
|
|
536
|
-
|
|
537
|
-
console.log("Count:", count()); # Only logs initial value
|
|
538
|
-
});
|
|
753
|
+
These were Signal-based reactive primitives from Jac:
|
|
539
754
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
755
|
+
```jac
|
|
756
|
+
# Legacy approach
|
|
757
|
+
let [count, setCount] = createSignal(0);
|
|
543
758
|
|
|
544
|
-
|
|
759
|
+
createEffect(lambda -> None {
|
|
760
|
+
console.log("Count:", count());
|
|
761
|
+
});
|
|
762
|
+
```
|
|
545
763
|
|
|
546
|
-
|
|
764
|
+
**Modern equivalent:**
|
|
547
765
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
- **Avoid**: Using `onMount()` for reactive side effects (use `createEffect()` instead)
|
|
766
|
+
```jac
|
|
767
|
+
# Modern approach with React hooks
|
|
768
|
+
let [count, setCount] = useState(0);
|
|
552
769
|
|
|
553
|
-
|
|
770
|
+
useEffect(lambda -> None {
|
|
771
|
+
console.log("Count:", count);
|
|
772
|
+
}, [count]);
|
|
773
|
+
```
|
|
554
774
|
|