@atom.io/template-react-node-backend 0.0.7 → 0.0.9

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.
package/dist/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>vite-react</title>
8
- <script type="module" crossorigin src="/assets/index-C0R4Gha-.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-C281ybwn.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-CqTfTEH3.css">
10
10
  </head>
11
11
  <body>
package/node/server.ts CHANGED
@@ -124,10 +124,10 @@ const server = http.createServer(async (req, res) => {
124
124
  {
125
125
  const id = Number.parseInt(searchParams.get(`id`) as string, 10)
126
126
  if (Number.isNaN(id)) {
127
- sendJSON(res, 200, { todos: getAllStmt.all() }, true)
127
+ sendJSON(res, 200, getAllStmt.all(), true)
128
128
  } else {
129
129
  const todo = getOneStmt.get(id)
130
- sendJSON(res, 200, { todo }, true)
130
+ sendJSON(res, 200, todo, true)
131
131
  }
132
132
  }
133
133
  return
@@ -137,7 +137,7 @@ const server = http.createServer(async (req, res) => {
137
137
  for await (const chunk of r) body += chunk
138
138
  const { lastInsertRowid } = insertStmt.run(body)
139
139
  const todo = getOneStmt.get(lastInsertRowid)
140
- sendJSON(res, 200, { todo }, true)
140
+ sendJSON(res, 200, todo, true)
141
141
  }
142
142
  return
143
143
  case `PUT`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atom.io/template-react-node-backend",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -9,11 +9,11 @@
9
9
  "react": "19.2.0",
10
10
  "react-dom": "19.2.0",
11
11
  "zod": "4.1.12",
12
- "atom.io": "0.44.6"
12
+ "atom.io": "0.44.7"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@types/node": "24.10.1",
16
- "@types/react": "19.2.5",
16
+ "@types/react": "19.2.6",
17
17
  "@types/react-dom": "19.2.3",
18
18
  "@vitejs/plugin-react": "5.1.1",
19
19
  "babel-plugin-react-compiler": "1.0.0",
@@ -23,8 +23,8 @@
23
23
  "eslint-plugin-react-refresh": "0.4.24",
24
24
  "globals": "16.5.0",
25
25
  "typescript": "5.9.3",
26
- "typescript-eslint": "8.46.4",
27
- "vite": "npm:rolldown-vite@7.2.5"
26
+ "typescript-eslint": "8.47.0",
27
+ "vite": "npm:rolldown-vite@7.2.6"
28
28
  },
29
29
  "scripts": {
30
30
  "dev": "concurrently vite ./node/server.ts ./node/authenticator.ts",
package/src/App.tsx CHANGED
@@ -21,20 +21,42 @@ const todoSchema = z.object({
21
21
  done: z.union([z.literal(0), z.literal(1)]), // keeps things simple with sqlite
22
22
  })
23
23
  type Todo = z.infer<typeof todoSchema>
24
+
25
+ /**
26
+ * This atom tells us what todos exist.
27
+ *
28
+ * `Loadable` means that the value of this atom may be a `Promise` sometimes.
29
+ *
30
+ * `useLoadable` from `"atom.io/react"` makes this easy to use in components.
31
+ */
24
32
  const todoKeysAtom = atom<Loadable<number[]>, Error>({
25
33
  key: `todoKeys`,
26
34
  default: async () => {
27
35
  const url = new URL(`/todos`, SERVER_URL)
28
36
  const response = await fetch(url, { credentials: `include` })
29
37
  if (!response.ok) throw new Error(response.status.toString())
30
- const json = (await response.json()) as { todos: unknown }
31
- const todos = todoSchema.array().parse(json.todos)
38
+ const json = await response.json()
39
+ const todos = todoSchema.array().parse(json)
40
+
41
+ /*
42
+ 🚀 Performance hack!
43
+ We can make our client less chatty by pre-populating our todo atoms.
44
+ Comment out this line and watch the network tab to see the difference.
45
+ */
32
46
  for (const todo of todos) setState(todoAtoms, todo.id, todo)
47
+
33
48
  return todos.map((todo) => todo.id)
34
49
  },
35
50
  catch: [Error],
36
51
  })
37
52
 
53
+ /**
54
+ * This atom family holds the values of individual todos.
55
+ *
56
+ * A todo's default value is fetched from the server, but
57
+ * this only happens when you `resetState`, or `getState`
58
+ * when the atom has never been gotten or set before.
59
+ */
38
60
  const todoAtoms = atomFamily<Loadable<Todo>, number, Error>({
39
61
  key: `todos`,
40
62
  default: async (id) => {
@@ -42,13 +64,14 @@ const todoAtoms = atomFamily<Loadable<Todo>, number, Error>({
42
64
  url.searchParams.set(`id`, id.toString())
43
65
  const response = await fetch(url, { credentials: `include` })
44
66
  if (!response.ok) throw new Error(response.status.toString())
45
- const json = (await response.json()) as { todo: unknown }
46
- const todo = todoSchema.parse(json.todo)
67
+ const json = await response.json()
68
+ const todo = todoSchema.parse(json)
47
69
  return todo
48
70
  },
49
71
  catch: [Error],
50
72
  })
51
73
 
74
+ /** This selector computes the total number of todos and the number of done todos. */
52
75
  const todosStatsSelector = selector<
53
76
  Loadable<{
54
77
  total: number
@@ -83,7 +106,7 @@ async function addTodo() {
83
106
  body: text,
84
107
  })
85
108
  if (!res.ok) throw new Error(res.status.toString())
86
- const { todo } = await res.json()
109
+ const todo = await res.json()
87
110
  const realId = todo.id
88
111
  setState(todoAtoms, realId, todo)
89
112
  setState(todoKeysAtom, async (loadable) => {
@@ -112,14 +135,29 @@ async function deleteTodo(todoKey: number) {
112
135
  })
113
136
  }
114
137
 
138
+ async function toggleTodoDone(todoKey: number, isNowDone: 0 | 1) {
139
+ const url = new URL(`todos`, SERVER_URL)
140
+ url.searchParams.set(`id`, todoKey.toString())
141
+ setState(todoAtoms, todoKey, async (loadable) => {
142
+ const prev = await loadable
143
+ if (Error.isError(prev)) return prev
144
+ return { ...prev, done: isNowDone } satisfies Todo
145
+ })
146
+ await fetch(url, {
147
+ method: `PUT`,
148
+ credentials: `include`,
149
+ body: isNowDone.toString(),
150
+ })
151
+ resetState(todoAtoms, todoKey)
152
+ }
153
+
115
154
  const TODO_FALLBACK: Todo = { id: 0, text: ``, done: 0 }
116
155
  const SKELETON_KEYS = Array.from({ length: 5 }).map(Math.random)
117
- for (const key of SKELETON_KEYS) setState(todoAtoms, key, TODO_FALLBACK)
156
+ for (const KEY of SKELETON_KEYS) setState(todoAtoms, KEY, TODO_FALLBACK)
118
157
 
119
158
  export function App(): React.JSX.Element {
120
159
  const todoKeys = useLoadable(todoKeysAtom, SKELETON_KEYS)
121
160
  const stats = useLoadable(todosStatsSelector, { total: 0, done: 0 })
122
-
123
161
  return (
124
162
  <>
125
163
  {todoKeys.error ? (
@@ -179,28 +217,11 @@ export function App(): React.JSX.Element {
179
217
  function Todo({ todoKey }: { todoKey: number }): React.JSX.Element {
180
218
  const todo = useLoadable(todoAtoms, todoKey, TODO_FALLBACK)
181
219
  const isSuspended = todo.loading || !Number.isInteger(todoKey)
182
- const toggleDone = useCallback(
183
- async (e: React.ChangeEvent<HTMLInputElement>) => {
184
- const url = new URL(`todos`, SERVER_URL)
185
- url.searchParams.set(`id`, todo.value.id.toString())
186
- const nowChecked = e.target.checked ? 1 : 0
187
- setState(todoAtoms, todoKey, async (loadable) => {
188
- const prev = await loadable
189
- if (Error.isError(prev)) return prev
190
- return { ...prev, done: nowChecked } satisfies Todo
191
- })
192
- await fetch(url, {
193
- method: `PUT`,
194
- credentials: `include`,
195
- body: nowChecked.toString(),
196
- })
197
- resetState(todoAtoms, todoKey)
198
- },
199
- [],
200
- )
201
- const deleteThisTodo = useCallback(async () => {
202
- await deleteTodo(todoKey)
220
+ const toggleDone = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
221
+ const isNowDone = e.target.checked ? 1 : 0
222
+ void toggleTodoDone(todoKey, isNowDone)
203
223
  }, [])
224
+ const deleteThisTodo = useCallback(() => deleteTodo(todoKey), [])
204
225
  return (
205
226
  <div className={cn(`todo`, isSuspended && `loading`)}>
206
227
  <input
@@ -220,11 +241,11 @@ function Todo({ todoKey }: { todoKey: number }): React.JSX.Element {
220
241
  )
221
242
  }
222
243
 
244
+ /** This atom holds the text of the new todo entry. */
223
245
  const newTodoTextAtom = atom<string>({
224
246
  key: `newTodo`,
225
247
  default: ``,
226
248
  })
227
-
228
249
  function NewTodo(): React.JSX.Element {
229
250
  const text = useO(newTodoTextAtom)
230
251
  const setText = useI(newTodoTextAtom)
@@ -250,6 +271,7 @@ function NewTodo(): React.JSX.Element {
250
271
  )
251
272
  }
252
273
 
274
+ /** This atom holds whether long load times are enabled. */
253
275
  const longLoadTimesAtom = atom<Loadable<boolean>>({
254
276
  key: `longLoadTimes`,
255
277
  default: () =>
@@ -257,7 +279,6 @@ const longLoadTimesAtom = atom<Loadable<boolean>>({
257
279
  credentials: `include`,
258
280
  }).then(async (res) => res.json()),
259
281
  })
260
-
261
282
  function LongLoadTimes(): React.JSX.Element {
262
283
  const longLoadTimes = useLoadable(longLoadTimesAtom, false)
263
284
  const toggle = useCallback(async () => {