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.
- jac_client/examples/all-in-one/app.jac +494 -347
- jac_client/examples/all-in-one/assets/workers/worker.py +5 -0
- jac_client/examples/all-in-one/button.jac +1 -1
- jac_client/examples/all-in-one/components/CategoryFilter.jac +35 -0
- jac_client/examples/all-in-one/components/Header.jac +13 -0
- jac_client/examples/all-in-one/components/ProfitOverview.jac +50 -0
- jac_client/examples/all-in-one/components/Summary.jac +53 -0
- jac_client/examples/all-in-one/components/TransactionForm.jac +158 -0
- jac_client/examples/all-in-one/components/TransactionItem.jac +55 -0
- jac_client/examples/all-in-one/components/TransactionList.jac +37 -0
- jac_client/examples/all-in-one/components/button.jac +1 -1
- jac_client/examples/all-in-one/components/navigation.jac +132 -0
- jac_client/examples/all-in-one/constants/categories.jac +37 -0
- jac_client/examples/all-in-one/constants/clients.jac +13 -0
- jac_client/examples/all-in-one/context/BudgetContext.jac +28 -0
- jac_client/examples/all-in-one/hooks/useBudget.jac +116 -0
- jac_client/examples/all-in-one/hooks/useLocalStorage.jac +36 -0
- jac_client/examples/all-in-one/pages/BudgetPlanner.cl.jac +70 -0
- jac_client/examples/all-in-one/pages/BudgetPlanner.jac +126 -0
- jac_client/examples/all-in-one/pages/FeaturesTest.cl.jac +552 -0
- jac_client/examples/all-in-one/pages/FeaturesTest.jac +126 -0
- jac_client/examples/all-in-one/pages/LandingPage.jac +101 -0
- jac_client/examples/all-in-one/pages/loginPage.jac +132 -0
- jac_client/examples/all-in-one/pages/nestedDemo.jac +61 -0
- jac_client/examples/all-in-one/pages/notFound.jac +24 -0
- jac_client/examples/all-in-one/pages/signupPage.jac +133 -0
- jac_client/examples/all-in-one/utils/formatters.jac +52 -0
- jac_client/examples/asset-serving/css-with-image/{app.jac → src/app.jac} +4 -4
- jac_client/examples/asset-serving/image-asset/{app.jac → src/app.jac} +4 -4
- jac_client/examples/asset-serving/import-alias/{app.jac → src/app.jac} +5 -5
- jac_client/examples/basic/{app.jac → src/app.jac} +4 -4
- jac_client/examples/basic-auth/src/app.jac +371 -0
- jac_client/examples/basic-auth-with-router/{app.jac → src/app.jac} +28 -28
- jac_client/examples/basic-full-stack/{app.jac → src/app.jac} +166 -127
- jac_client/examples/css-styling/js-styling/{app.jac → src/app.jac} +7 -7
- jac_client/examples/css-styling/material-ui/{app.jac → src/app.jac} +6 -6
- jac_client/examples/css-styling/pure-css/{app.jac → src/app.jac} +7 -7
- jac_client/examples/css-styling/sass-example/{app.jac → src/app.jac} +7 -7
- jac_client/examples/css-styling/styled-components/{app.jac → src/app.jac} +6 -6
- jac_client/examples/css-styling/tailwind-example/{app.jac → src/app.jac} +7 -7
- jac_client/examples/full-stack-with-auth/{app.jac → src/app.jac} +47 -47
- jac_client/examples/little-x/{app.jac → src/app.jac} +27 -32
- jac_client/examples/little-x/src/submit-button.jac +16 -0
- jac_client/examples/nested-folders/nested-advance/{ButtonRoot.jac → src/ButtonRoot.jac} +1 -1
- jac_client/examples/nested-folders/nested-advance/{app.jac → src/app.jac} +1 -1
- jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/ButtonSecondL.jac +1 -1
- jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/Card.jac +1 -1
- jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/level2/ButtonThirdL.jac +1 -1
- jac_client/examples/nested-folders/nested-basic/{app.jac → src/app.jac} +2 -2
- jac_client/examples/nested-folders/nested-basic/{button.jac → src/button.jac} +1 -1
- jac_client/examples/nested-folders/nested-basic/{components → src/components}/button.jac +1 -1
- jac_client/examples/ts-support/src/app.jac +35 -0
- jac_client/examples/with-router/{app.jac → src/app.jac} +15 -15
- jac_client/plugin/cli.jac +504 -0
- jac_client/plugin/client.jac +45 -0
- jac_client/plugin/client_runtime.cl.jac +42 -0
- jac_client/plugin/impl/client.impl.jac +193 -0
- jac_client/plugin/impl/client_runtime.impl.jac +195 -0
- jac_client/plugin/impl/vite_client_bundle.impl.jac +72 -0
- jac_client/plugin/plugin_config.jac +195 -0
- jac_client/plugin/src/__init__.jac +20 -0
- jac_client/plugin/src/asset_processor.jac +33 -0
- jac_client/plugin/src/babel_processor.jac +18 -0
- jac_client/plugin/src/compiler.jac +67 -0
- jac_client/plugin/src/config_loader.jac +32 -0
- jac_client/plugin/src/impl/asset_processor.impl.jac +127 -0
- jac_client/plugin/src/impl/babel_processor.impl.jac +89 -0
- jac_client/plugin/src/impl/compiler.impl.jac +288 -0
- jac_client/plugin/src/impl/config_loader.impl.jac +119 -0
- jac_client/plugin/src/impl/import_processor.impl.jac +33 -0
- jac_client/plugin/src/impl/jac_to_js.impl.jac +41 -0
- jac_client/plugin/src/impl/package_installer.impl.jac +105 -0
- jac_client/plugin/src/impl/vite_bundler.impl.jac +626 -0
- jac_client/plugin/src/import_processor.jac +19 -0
- jac_client/plugin/src/jac_to_js.jac +35 -0
- jac_client/plugin/src/package_installer.jac +26 -0
- jac_client/plugin/src/vite_bundler.jac +44 -0
- jac_client/plugin/vite_client_bundle.jac +31 -0
- jac_client/tests/conftest.py +283 -0
- jac_client/tests/fixtures/basic-app/app.jac +2 -2
- jac_client/tests/fixtures/cl_file/app.cl.jac +2 -2
- jac_client/tests/fixtures/client_app_with_antd/app.jac +1 -1
- jac_client/tests/fixtures/js_import/app.jac +5 -5
- jac_client/tests/fixtures/spawn_test/app.jac +15 -18
- jac_client/tests/fixtures/with-ts/app.jac +35 -0
- jac_client/tests/test_cli.py +811 -0
- jac_client/tests/test_it.py +592 -97
- {jac_client-0.2.3.dist-info → jac_client-0.2.8.dist-info}/METADATA +41 -34
- jac_client-0.2.8.dist-info/RECORD +97 -0
- {jac_client-0.2.3.dist-info → jac_client-0.2.8.dist-info}/WHEEL +2 -1
- jac_client-0.2.8.dist-info/entry_points.txt +4 -0
- jac_client-0.2.8.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/assets/burger.png +0 -0
- 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/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/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/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/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/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/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/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/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/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/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/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/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/package.json +0 -28
- jac_client/examples/full-stack-with-auth/vite.config.js +0 -29
- 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/README.md +0 -77
- 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.js +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/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/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/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,530 +0,0 @@
|
|
|
1
|
-
# Step 5: Local State
|
|
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 **state** - the data that makes your app interactive and dynamic!
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Part 1: Building the App
|
|
10
|
-
|
|
11
|
-
### Step 5.1: First, Let's See Why Normal Variables Don't Work
|
|
12
|
-
|
|
13
|
-
Let's try using a normal variable to track todos:
|
|
14
|
-
|
|
15
|
-
```jac
|
|
16
|
-
cl {
|
|
17
|
-
# ... (keep all your components from Step 4)
|
|
18
|
-
|
|
19
|
-
def app() -> any {
|
|
20
|
-
# Try using a normal variable
|
|
21
|
-
let todos = [
|
|
22
|
-
{"text": "Learn Jac", "done": false},
|
|
23
|
-
{"text": "Build app", "done": false}
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
return <div style={{
|
|
27
|
-
"maxWidth": "600px",
|
|
28
|
-
"margin": "20px auto",
|
|
29
|
-
"padding": "20px"
|
|
30
|
-
}}>
|
|
31
|
-
<h1>My Todos ({todos.length})</h1>
|
|
32
|
-
<p>Todos: {todos.length}</p>
|
|
33
|
-
</div>;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
This works for displaying data, but **what if we want to change it?** Normal variables can't trigger UI updates!
|
|
39
|
-
|
|
40
|
-
### Step 5.2: Introducing `useState`
|
|
41
|
-
|
|
42
|
-
To make data interactive, we need `useState`. First, import it:
|
|
43
|
-
|
|
44
|
-
```jac
|
|
45
|
-
cl import from react {useState}
|
|
46
|
-
|
|
47
|
-
cl {
|
|
48
|
-
def app() -> any {
|
|
49
|
-
# Create state with useState
|
|
50
|
-
let [todos, setTodos] = useState([]);
|
|
51
|
-
|
|
52
|
-
return <div style={{"padding": "20px"}}>
|
|
53
|
-
<h1>My Todos</h1>
|
|
54
|
-
<p>Total: {todos.length}</p>
|
|
55
|
-
</div>;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
**What's happening:**
|
|
61
|
-
- `useState([])` creates state with initial value `[]` (empty array)
|
|
62
|
-
- Returns two things:
|
|
63
|
-
- `todos` - The current value (read-only)
|
|
64
|
-
- `setTodos` - Function to update the value
|
|
65
|
-
|
|
66
|
-
### Step 5.3: Add State for Input Field
|
|
67
|
-
|
|
68
|
-
Let's make the input field work:
|
|
69
|
-
|
|
70
|
-
```jac
|
|
71
|
-
cl import from react {useState}
|
|
72
|
-
|
|
73
|
-
cl {
|
|
74
|
-
def TodoInput(props: any) -> any {
|
|
75
|
-
return <div style={{
|
|
76
|
-
"display": "flex",
|
|
77
|
-
"gap": "8px",
|
|
78
|
-
"marginBottom": "16px"
|
|
79
|
-
}}>
|
|
80
|
-
<input
|
|
81
|
-
type="text"
|
|
82
|
-
value={props.input}
|
|
83
|
-
placeholder="What needs to be done?"
|
|
84
|
-
style={{
|
|
85
|
-
"flex": "1",
|
|
86
|
-
"padding": "8px",
|
|
87
|
-
"border": "1px solid #ddd",
|
|
88
|
-
"borderRadius": "4px"
|
|
89
|
-
}}
|
|
90
|
-
/>
|
|
91
|
-
<button style={{
|
|
92
|
-
"padding": "8px 16px",
|
|
93
|
-
"background": "#3b82f6",
|
|
94
|
-
"color": "#ffffff",
|
|
95
|
-
"border": "none",
|
|
96
|
-
"borderRadius": "4px",
|
|
97
|
-
"cursor": "pointer"
|
|
98
|
-
}}>
|
|
99
|
-
Add
|
|
100
|
-
</button>
|
|
101
|
-
</div>;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
def app() -> any {
|
|
105
|
-
# State for input field
|
|
106
|
-
let [input, setInput] = useState("");
|
|
107
|
-
|
|
108
|
-
return <div style={{
|
|
109
|
-
"maxWidth": "600px",
|
|
110
|
-
"margin": "20px auto",
|
|
111
|
-
"padding": "20px"
|
|
112
|
-
}}>
|
|
113
|
-
<h1>My Todos</h1>
|
|
114
|
-
<TodoInput input={input} />
|
|
115
|
-
<p>You typed: {input}</p>
|
|
116
|
-
</div>;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
**Try typing in the input!** Nothing happens yet because we haven't connected the onChange event (we'll do that in the next step).
|
|
122
|
-
|
|
123
|
-
### Step 5.4: Add State for Todos List
|
|
124
|
-
|
|
125
|
-
Now let's track our todos list with state:
|
|
126
|
-
|
|
127
|
-
```jac
|
|
128
|
-
cl import from react {useState}
|
|
129
|
-
|
|
130
|
-
cl {
|
|
131
|
-
def TodoItem(props: any) -> any {
|
|
132
|
-
return <div style={{
|
|
133
|
-
"display": "flex",
|
|
134
|
-
"alignItems": "center",
|
|
135
|
-
"gap": "10px",
|
|
136
|
-
"padding": "10px",
|
|
137
|
-
"borderBottom": "1px solid #e5e7eb"
|
|
138
|
-
}}>
|
|
139
|
-
<input type="checkbox" checked={props.done} />
|
|
140
|
-
<span style={{
|
|
141
|
-
"flex": "1",
|
|
142
|
-
"textDecoration": ("line-through" if props.done else "none"),
|
|
143
|
-
"color": ("#999" if props.done else "#000")
|
|
144
|
-
}}>
|
|
145
|
-
{props.text}
|
|
146
|
-
</span>
|
|
147
|
-
<button style={{
|
|
148
|
-
"padding": "4px 8px",
|
|
149
|
-
"background": "#ef4444",
|
|
150
|
-
"color": "white",
|
|
151
|
-
"border": "none",
|
|
152
|
-
"borderRadius": "4px",
|
|
153
|
-
"cursor": "pointer"
|
|
154
|
-
}}>
|
|
155
|
-
Delete
|
|
156
|
-
</button>
|
|
157
|
-
</div>;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
def app() -> any {
|
|
161
|
-
# State for todos
|
|
162
|
-
let [todos, setTodos] = useState([
|
|
163
|
-
{"text": "Learn Jac basics", "done": false},
|
|
164
|
-
{"text": "Build a todo app", "done": false}
|
|
165
|
-
]);
|
|
166
|
-
|
|
167
|
-
return <div style={{
|
|
168
|
-
"maxWidth": "600px",
|
|
169
|
-
"margin": "20px auto",
|
|
170
|
-
"padding": "20px",
|
|
171
|
-
"background": "#ffffff",
|
|
172
|
-
"borderRadius": "8px"
|
|
173
|
-
}}>
|
|
174
|
-
<h1>My Todos</h1>
|
|
175
|
-
|
|
176
|
-
# Display todos
|
|
177
|
-
<div>
|
|
178
|
-
{todos.map(lambda todo: any -> any {
|
|
179
|
-
return <TodoItem
|
|
180
|
-
text={todo.text}
|
|
181
|
-
done={todo.done}
|
|
182
|
-
/>;
|
|
183
|
-
})}
|
|
184
|
-
</div>
|
|
185
|
-
|
|
186
|
-
# Stats
|
|
187
|
-
<div style={{"marginTop": "16px", "color": "#666"}}>
|
|
188
|
-
{todos.length} items total
|
|
189
|
-
</div>
|
|
190
|
-
</div>;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### Step 5.5: Add State for Filter
|
|
196
|
-
|
|
197
|
-
Let's add filter state:
|
|
198
|
-
|
|
199
|
-
```jac
|
|
200
|
-
cl import from react {useState}
|
|
201
|
-
|
|
202
|
-
cl {
|
|
203
|
-
# ... (keep all previous components)
|
|
204
|
-
|
|
205
|
-
def TodoFilters(props: any) -> any {
|
|
206
|
-
return <div style={{
|
|
207
|
-
"display": "flex",
|
|
208
|
-
"gap": "8px",
|
|
209
|
-
"marginBottom": "16px"
|
|
210
|
-
}}>
|
|
211
|
-
<button style={{
|
|
212
|
-
"padding": "6px 12px",
|
|
213
|
-
"background": ("#3b82f6" if props.filter == "all" else "#e5e7eb"),
|
|
214
|
-
"color": ("#ffffff" if props.filter == "all" else "#000000"),
|
|
215
|
-
"border": "none",
|
|
216
|
-
"borderRadius": "4px",
|
|
217
|
-
"cursor": "pointer"
|
|
218
|
-
}}>
|
|
219
|
-
All
|
|
220
|
-
</button>
|
|
221
|
-
<button style={{
|
|
222
|
-
"padding": "6px 12px",
|
|
223
|
-
"background": ("#3b82f6" if props.filter == "active" else "#e5e7eb"),
|
|
224
|
-
"color": ("#ffffff" if props.filter == "active" else "#000000"),
|
|
225
|
-
"border": "none",
|
|
226
|
-
"borderRadius": "4px",
|
|
227
|
-
"cursor": "pointer"
|
|
228
|
-
}}>
|
|
229
|
-
Active
|
|
230
|
-
</button>
|
|
231
|
-
<button style={{
|
|
232
|
-
"padding": "6px 12px",
|
|
233
|
-
"background": ("#3b82f6" if props.filter == "completed" else "#e5e7eb"),
|
|
234
|
-
"color": ("#ffffff" if props.filter == "completed" else "#000000"),
|
|
235
|
-
"border": "none",
|
|
236
|
-
"borderRadius": "4px",
|
|
237
|
-
"cursor": "pointer"
|
|
238
|
-
}}>
|
|
239
|
-
Completed
|
|
240
|
-
</button>
|
|
241
|
-
</div>;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
def app() -> any {
|
|
245
|
-
let [todos, setTodos] = useState([
|
|
246
|
-
{"text": "Learn Jac basics", "done": false},
|
|
247
|
-
{"text": "Build a todo app", "done": true}
|
|
248
|
-
]);
|
|
249
|
-
let [filter, setFilter] = useState("all");
|
|
250
|
-
|
|
251
|
-
return <div style={{
|
|
252
|
-
"maxWidth": "600px",
|
|
253
|
-
"margin": "20px auto",
|
|
254
|
-
"padding": "20px"
|
|
255
|
-
}}>
|
|
256
|
-
<h1>My Todos</h1>
|
|
257
|
-
<TodoFilters filter={filter} />
|
|
258
|
-
|
|
259
|
-
# Show current filter
|
|
260
|
-
<p>Current filter: {filter}</p>
|
|
261
|
-
</div>;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
**Notice:** The filter buttons now highlight based on the current filter! But clicking them doesn't work yet (we'll add that in Step 6).
|
|
267
|
-
|
|
268
|
-
---
|
|
269
|
-
|
|
270
|
-
**⏭ Want to skip the theory?** Jump to [Step 6: Event Handlers](./step-06-events.md)
|
|
271
|
-
|
|
272
|
-
---
|
|
273
|
-
|
|
274
|
-
## Part 2: Understanding the Concepts
|
|
275
|
-
|
|
276
|
-
### What is State?
|
|
277
|
-
|
|
278
|
-
**State** is data that can change over time and causes your UI to update when it changes.
|
|
279
|
-
|
|
280
|
-
**Python analogy:**
|
|
281
|
-
|
|
282
|
-
```python
|
|
283
|
-
# Python class with state
|
|
284
|
-
class TodoApp:
|
|
285
|
-
def __init__(self):
|
|
286
|
-
self.todos = [] # This is state
|
|
287
|
-
|
|
288
|
-
def add_todo(self, text):
|
|
289
|
-
self.todos.append(text) # Changing state
|
|
290
|
-
self.render() # Manually update UI
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
```jac
|
|
294
|
-
# Jac with React
|
|
295
|
-
def app() -> any {
|
|
296
|
-
let [todos, setTodos] = useState([]); # This is state
|
|
297
|
-
|
|
298
|
-
# When you call setTodos(), React automatically updates the UI!
|
|
299
|
-
}
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
### The `useState` Hook
|
|
303
|
-
|
|
304
|
-
```jac
|
|
305
|
-
let [value, setValue] = useState(initialValue);
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
**Returns a pair:**
|
|
309
|
-
1. `value` - Current state value (read-only, don't modify directly!)
|
|
310
|
-
2. `setValue` - Function to update state
|
|
311
|
-
|
|
312
|
-
**Examples:**
|
|
313
|
-
|
|
314
|
-
```jac
|
|
315
|
-
# String state
|
|
316
|
-
let [name, setName] = useState("Alice");
|
|
317
|
-
|
|
318
|
-
# Number state
|
|
319
|
-
let [count, setCount] = useState(0);
|
|
320
|
-
|
|
321
|
-
# Boolean state
|
|
322
|
-
let [isOpen, setIsOpen] = useState(false);
|
|
323
|
-
|
|
324
|
-
# Array state
|
|
325
|
-
let [todos, setTodos] = useState([]);
|
|
326
|
-
|
|
327
|
-
# Object state
|
|
328
|
-
let [user, setUser] = useState({"name": "Alice", "age": 30});
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### Why Use `useState`?
|
|
332
|
-
|
|
333
|
-
**Without useState (doesn't work):**
|
|
334
|
-
|
|
335
|
-
```jac
|
|
336
|
-
def app() -> any {
|
|
337
|
-
let count = 0; # Normal variable
|
|
338
|
-
|
|
339
|
-
# Button click would change count, but UI won't update!
|
|
340
|
-
return <button>Count: {count}</button>;
|
|
341
|
-
}
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
**With useState (works!):**
|
|
345
|
-
|
|
346
|
-
```jac
|
|
347
|
-
def app() -> any {
|
|
348
|
-
let [count, setCount] = useState(0); # State
|
|
349
|
-
|
|
350
|
-
# When setCount is called, React re-renders the component!
|
|
351
|
-
return <button>Count: {count}</button>;
|
|
352
|
-
}
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
### Multiple State Variables
|
|
356
|
-
|
|
357
|
-
You can have multiple pieces of state:
|
|
358
|
-
|
|
359
|
-
```jac
|
|
360
|
-
def app() -> any {
|
|
361
|
-
let [todos, setTodos] = useState([]);
|
|
362
|
-
let [input, setInput] = useState("");
|
|
363
|
-
let [filter, setFilter] = useState("all");
|
|
364
|
-
let [loading, setLoading] = useState(false);
|
|
365
|
-
|
|
366
|
-
# Use them independently
|
|
367
|
-
}
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
Each state variable is independent and has its own update function.
|
|
371
|
-
|
|
372
|
-
### State Naming Convention
|
|
373
|
-
|
|
374
|
-
Follow this pattern:
|
|
375
|
-
|
|
376
|
-
```jac
|
|
377
|
-
# Pattern: [thing, setThing]
|
|
378
|
-
let [count, setCount] = useState(0);
|
|
379
|
-
let [name, setName] = useState("");
|
|
380
|
-
let [isOpen, setIsOpen] = useState(false);
|
|
381
|
-
let [todos, setTodos] = useState([]);
|
|
382
|
-
|
|
383
|
-
# Bad names
|
|
384
|
-
let [count, updateCount] = useState(0); # Inconsistent
|
|
385
|
-
let [x, y] = useState(0); # Not descriptive
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### The `.map()` Method for Lists
|
|
389
|
-
|
|
390
|
-
To render a list of items, use `.map()`:
|
|
391
|
-
|
|
392
|
-
```jac
|
|
393
|
-
{todos.map(lambda todo: any -> any {
|
|
394
|
-
return <TodoItem text={todo.text} done={todo.done} />;
|
|
395
|
-
})}
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
**How it works:**
|
|
399
|
-
|
|
400
|
-
```python
|
|
401
|
-
# Python equivalent
|
|
402
|
-
todos = [{"text": "Task 1"}, {"text": "Task 2"}]
|
|
403
|
-
items = [TodoItem(text=todo["text"]) for todo in todos]
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
**Breakdown:**
|
|
407
|
-
- `todos.map(...)` - Loop through each todo
|
|
408
|
-
- `lambda todo: any -> any { ... }` - Function that runs for each item
|
|
409
|
-
- `return <TodoItem ... />` - Returns a component for each item
|
|
410
|
-
|
|
411
|
-
### State is Immutable
|
|
412
|
-
|
|
413
|
-
**Never modify state directly:**
|
|
414
|
-
|
|
415
|
-
```jac
|
|
416
|
-
# WRONG - Never do this!
|
|
417
|
-
let [todos, setTodos] = useState([]);
|
|
418
|
-
todos.push(newTodo); # DON'T modify directly!
|
|
419
|
-
|
|
420
|
-
# CORRECT - Create new array
|
|
421
|
-
let [todos, setTodos] = useState([]);
|
|
422
|
-
setTodos(todos.concat([newTodo])); # Create new array
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
Why? Because React needs to detect changes to update the UI. If you modify directly, React won't know it changed!
|
|
426
|
-
|
|
427
|
-
### Passing State to Children
|
|
428
|
-
|
|
429
|
-
State flows down through props:
|
|
430
|
-
|
|
431
|
-
```jac
|
|
432
|
-
def Parent() -> any {
|
|
433
|
-
let [name, setName] = useState("Alice");
|
|
434
|
-
|
|
435
|
-
# Pass state down as props
|
|
436
|
-
return <Child name={name} />;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
def Child(props: any) -> any {
|
|
440
|
-
# Access state via props
|
|
441
|
-
return <div>Hello, {props.name}!</div>;
|
|
442
|
-
}
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
The child receives state but **cannot modify** the parent's state directly (we'll learn how to do that with callbacks in the next step).
|
|
446
|
-
|
|
447
|
-
---
|
|
448
|
-
|
|
449
|
-
## What You've Learned
|
|
450
|
-
|
|
451
|
-
- What state is and why we need it
|
|
452
|
-
- How to use the `useState` hook
|
|
453
|
-
- Creating multiple state variables
|
|
454
|
-
- State naming conventions
|
|
455
|
-
- Using `.map()` to render lists
|
|
456
|
-
- State is immutable (don't modify directly)
|
|
457
|
-
- Passing state to child components via props
|
|
458
|
-
|
|
459
|
-
---
|
|
460
|
-
|
|
461
|
-
## Common Issues
|
|
462
|
-
|
|
463
|
-
### Issue: UI not updating when state changes
|
|
464
|
-
|
|
465
|
-
**Check:** Are you modifying state directly?
|
|
466
|
-
|
|
467
|
-
```jac
|
|
468
|
-
# Wrong
|
|
469
|
-
todos.push(newTodo);
|
|
470
|
-
|
|
471
|
-
# Correct
|
|
472
|
-
setTodos(todos.concat([newTodo]));
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
### Issue: "todos is not iterable"
|
|
476
|
-
|
|
477
|
-
**Check:** Did you initialize state as an array?
|
|
478
|
-
|
|
479
|
-
```jac
|
|
480
|
-
# Wrong
|
|
481
|
-
let [todos, setTodos] = useState(); # undefined
|
|
482
|
-
|
|
483
|
-
# Correct
|
|
484
|
-
let [todos, setTodos] = useState([]); # empty array
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
### Issue: useState is not defined
|
|
488
|
-
|
|
489
|
-
**Check:** Did you import it?
|
|
490
|
-
|
|
491
|
-
```jac
|
|
492
|
-
cl import from react {useState}
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
---
|
|
496
|
-
|
|
497
|
-
## Quick Exercise
|
|
498
|
-
|
|
499
|
-
Try adding more initial todos:
|
|
500
|
-
|
|
501
|
-
```jac
|
|
502
|
-
let [todos, setTodos] = useState([
|
|
503
|
-
{"text": "Learn Jac basics", "done": true},
|
|
504
|
-
{"text": "Build a todo app", "done": false},
|
|
505
|
-
{"text": "Deploy to production", "done": false},
|
|
506
|
-
{"text": "Celebrate!", "done": false}
|
|
507
|
-
]);
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
And display the count of completed todos:
|
|
511
|
-
|
|
512
|
-
```jac
|
|
513
|
-
let completedCount = todos.filter(lambda todo: any -> bool {
|
|
514
|
-
return todo.done;
|
|
515
|
-
}).length;
|
|
516
|
-
|
|
517
|
-
return <div>
|
|
518
|
-
<p>{completedCount} completed out of {todos.length}</p>
|
|
519
|
-
</div>;
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
---
|
|
523
|
-
|
|
524
|
-
## Next Step
|
|
525
|
-
|
|
526
|
-
Great! You now have state in your app, but you can't change it yet. Clicking buttons does nothing!
|
|
527
|
-
|
|
528
|
-
In the next step, we'll add **event handlers** to make your app fully interactive!
|
|
529
|
-
|
|
530
|
-
**[Continue to Step 6: Event Handlers](./step-06-events.md)**
|