juststore 1.1.1 → 1.2.0

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/README.md CHANGED
@@ -21,56 +21,56 @@ bun add juststore
21
21
  ## Quick Start
22
22
 
23
23
  ```tsx
24
- import { createStore } from "juststore";
25
- import { toast } from "sonner";
24
+ import { createStore } from 'juststore'
25
+ import { toast } from 'sonner'
26
26
 
27
27
  type AppState = {
28
28
  user: {
29
- name: string;
29
+ name: string
30
30
  preferences: {
31
- theme: "light" | "dark";
32
- };
33
- };
34
- todos: { id: number; text: string; done: boolean }[];
35
- };
31
+ theme: 'light' | 'dark'
32
+ }
33
+ }
34
+ todos: { id: number; text: string; done: boolean }[]
35
+ }
36
36
 
37
- const store = createStore<AppState>("app", {
37
+ const store = createStore<AppState>('app', {
38
38
  user: {
39
- name: "Guest",
40
- preferences: { theme: "light" },
39
+ name: 'Guest',
40
+ preferences: { theme: 'light' }
41
41
  },
42
- todos: [],
43
- });
42
+ todos: []
43
+ })
44
44
 
45
45
  async function initUserDetails() {
46
- const response = await fetch("/api/user/details");
47
- const data = (await response.json()) as AppState["user"];
48
- store.user.set(data);
46
+ const response = await fetch('/api/user/details')
47
+ const data = (await response.json()) as AppState['user']
48
+ store.user.set(data)
49
49
  }
50
50
 
51
51
  function ThemeToggle() {
52
- const theme = store.user.preferences.theme.use();
53
- const nextTheme = theme === "light" ? "dark" : "light";
52
+ const theme = store.user.preferences.theme.use()
53
+ const nextTheme = theme === 'light' ? 'dark' : 'light'
54
54
 
55
55
  const updateTheme = async () => {
56
56
  try {
57
- const response = await fetch("/api/user/preferences/theme", {
58
- method: "PUT",
59
- headers: { "Content-Type": "application/json" },
60
- body: JSON.stringify({ theme: nextTheme }),
61
- });
57
+ const response = await fetch('/api/user/preferences/theme', {
58
+ method: 'PUT',
59
+ headers: { 'Content-Type': 'application/json' },
60
+ body: JSON.stringify({ theme: nextTheme })
61
+ })
62
62
 
63
63
  if (!response.ok) {
64
- throw new Error("Theme update failed");
64
+ throw new Error('Theme update failed')
65
65
  }
66
66
 
67
- store.user.preferences.theme.set(nextTheme);
67
+ store.user.preferences.theme.set(nextTheme)
68
68
  } catch {
69
- toast.error("Failed to update theme");
69
+ toast.error('Failed to update theme')
70
70
  }
71
- };
71
+ }
72
72
 
73
- return <button onClick={updateTheme}>Theme: {theme}</button>;
73
+ return <button onClick={updateTheme}>Theme: {theme}</button>
74
74
  }
75
75
  ```
76
76
 
@@ -80,70 +80,65 @@ function ThemeToggle() {
80
80
 
81
81
  ```tsx
82
82
  type SearchState = {
83
- query: string;
84
- category: "all" | "running" | "stopped";
85
- services: { id: string; name: string; status: "running" | "stopped" }[];
86
- };
83
+ query: string
84
+ category: 'all' | 'running' | 'stopped'
85
+ services: { id: string; name: string; status: 'running' | 'stopped' }[]
86
+ }
87
87
 
88
- const searchStore = createStore<SearchState>("services-search", {
89
- query: "",
90
- category: "all",
91
- services: [],
92
- });
88
+ const searchStore = createStore<SearchState>('services-search', {
89
+ query: '',
90
+ category: 'all',
91
+ services: []
92
+ })
93
93
 
94
94
  function SearchQueryInput() {
95
- const query = searchStore.query.use() ?? "";
95
+ const query = searchStore.query.use() ?? ''
96
96
  return (
97
97
  <input
98
98
  value={query}
99
- onChange={(e) => searchStore.query.set(e.target.value)}
99
+ onChange={e => searchStore.query.set(e.target.value)}
100
100
  placeholder="Search services"
101
101
  />
102
- );
102
+ )
103
103
  }
104
104
 
105
105
  function SearchCategoryFilter() {
106
- const category = searchStore.category.use();
106
+ const category = searchStore.category.use()
107
107
  return (
108
108
  <select
109
109
  value={category}
110
- onChange={(e) =>
111
- searchStore.category.set(e.target.value as SearchState["category"])
112
- }
110
+ onChange={e => searchStore.category.set(e.target.value as SearchState['category'])}
113
111
  >
114
112
  <option value="all">All</option>
115
113
  <option value="running">Running</option>
116
114
  <option value="stopped">Stopped</option>
117
115
  </select>
118
- );
116
+ )
119
117
  }
120
118
 
121
119
  function SearchResults() {
122
- const query = searchStore.query.useDebounce(150) ?? "";
123
- const category = searchStore.category.use();
120
+ const query = searchStore.query.useDebounce(150) ?? ''
121
+ const category = searchStore.category.use()
124
122
 
125
123
  const visible = searchStore.services.useCompute(
126
- (services) => {
127
- const list = services ?? [];
128
- return list.filter((service) => {
129
- const nameMatch = service.name
130
- .toLowerCase()
131
- .includes(query.toLowerCase());
132
- const categoryMatch =
133
- category === "all" ? true : service.status === category;
134
- return nameMatch && categoryMatch;
135
- });
124
+ services => {
125
+ const list = services ?? []
126
+ return list.filter(service => {
127
+ const nameMatch = service.name.toLowerCase().includes(query.toLowerCase())
128
+ const categoryMatch = category === 'all' ? true : service.status === category
129
+ return nameMatch && categoryMatch
130
+ })
136
131
  },
137
- [query, category],
138
- );
132
+ [query, category]
133
+ )
139
134
 
140
135
  return (
141
136
  <ul>
142
- {visible.map((service) => (
137
+ {visible.map(service => (
143
138
  <li key={service.id}>{service.name}</li>
144
139
  ))}
145
140
  </ul>
146
- );
141
+ )
147
142
  }
148
143
 
149
144
  function ServiceSearchPage() {
@@ -153,40 +148,40 @@ function ServiceSearchPage() {
153
148
  <SearchCategoryFilter />
154
149
  <SearchResults />
155
150
  </>
156
- );
151
+ )
157
152
  }
158
153
  ```
159
154
 
160
155
  ### 2) WebSocket ingestion into normalized state
161
156
 
162
157
  ```tsx
163
- type RouteUptime = { alias: string; uptime: number };
158
+ type RouteUptime = { alias: string; uptime: number }
164
159
  type UptimeState = {
165
- routeKeys: string[];
166
- uptimeByAlias: Record<string, RouteUptime>;
167
- };
160
+ routeKeys: string[]
161
+ uptimeByAlias: Record<string, RouteUptime>
162
+ }
168
163
 
169
- const uptimeStore = createStore<UptimeState>("uptime", {
164
+ const uptimeStore = createStore<UptimeState>('uptime', {
170
165
  routeKeys: [],
171
- uptimeByAlias: {},
172
- });
166
+ uptimeByAlias: {}
167
+ })
173
168
 
174
169
  function onUptimeMessage(rows: RouteUptime[]) {
175
- const keys = rows.map((row) => row.alias).toSorted();
176
- uptimeStore.routeKeys.set(keys);
170
+ const keys = rows.map(row => row.alias).toSorted()
171
+ uptimeStore.routeKeys.set(keys)
177
172
 
178
173
  uptimeStore.uptimeByAlias.set(
179
174
  rows.reduce<Record<string, RouteUptime>>((acc, row) => {
180
- acc[row.alias] = row;
181
- return acc;
182
- }, {}),
183
- );
175
+ acc[row.alias] = row
176
+ return acc
177
+ }, {})
178
+ )
184
179
  }
185
180
 
186
181
  // fine grained subscription
187
182
  function UptimeComponent({ alias }: { alias: string }) {
188
- const uptime = uptimeStore.uptimeByAlias[alias]?.uptime.use();
189
- return <div>Uptime: {uptime ?? "Unknown"}</div>;
183
+ const uptime = uptimeStore.uptimeByAlias[alias]?.uptime.use()
184
+ return <div>Uptime: {uptime ?? 'Unknown'}</div>
190
185
  }
191
186
  ```
192
187
 
@@ -194,72 +189,62 @@ function UptimeComponent({ alias }: { alias: string }) {
194
189
 
195
190
  ```tsx
196
191
  type HeaderState = {
197
- headers: Record<string, string>;
198
- };
192
+ headers: Record<string, string>
193
+ }
199
194
 
200
- const headerStore = createStore<HeaderState>("route-headers", {
201
- headers: {},
202
- });
195
+ const headerStore = createStore<HeaderState>('route-headers', {
196
+ headers: {}
197
+ })
203
198
 
204
199
  function HeadersEditor() {
205
200
  // keys is a virtual property that returns a state proxy for the keys array
206
201
  // it only recomputes when the keys array changes
207
- const keys = headerStore.headers.keys.use();
202
+ const keys = headerStore.headers.keys.use()
208
203
 
209
204
  return (
210
205
  <div>
211
- {keys.map((key) => (
206
+ {keys.map(key => (
212
207
  <div key={key}>
213
208
  <input
214
209
  value={key}
215
- onChange={(e) =>
216
- headerStore.headers.rename(key, e.target.value.trim())
217
- }
210
+ onChange={e => headerStore.headers.rename(key, e.target.value.trim())}
218
211
  />
219
212
  {/* Render and update without cascade rerendering the entire HeadersEditor */}
220
213
  <RenderWithUpdate state={headerStore.headers[key]}>
221
- {(value, update) => (
222
- <input value={value} onChange={(e) => update(e.target.value)} />
223
- )}
214
+ {(value, update) => <input value={value} onChange={e => update(e.target.value)} />}
224
215
  </RenderWithUpdate>
225
- <button onClick={() => headerStore.headers[key].reset()}>
226
- remove
227
- </button>
216
+ <button onClick={() => headerStore.headers[key].reset()}>remove</button>
228
217
  </div>
229
218
  ))}
230
219
  </div>
231
- );
220
+ )
232
221
  }
233
222
  ```
234
223
 
235
224
  ### 4) Typed form with validation and submit gating
236
225
 
237
226
  ```tsx
238
- import { useForm } from "juststore";
239
- import {
240
- StoreFormInputField,
241
- StoreFormPasswordField,
242
- } from "@/components/store/Input"; // from juststore-shadcn
227
+ import { useForm } from 'juststore'
228
+ import { StoreFormInputField, StoreFormPasswordField } from '@/components/store/Input' // from juststore-shadcn
243
229
 
244
230
  type LoginForm = {
245
- email: string;
246
- password: string;
247
- };
231
+ email: string
232
+ password: string
233
+ }
248
234
 
249
235
  function LoginPage() {
250
236
  const form = useForm<LoginForm>(
251
- { email: "", password: "" },
237
+ { email: '', password: '' },
252
238
  {
253
239
  email: { validate: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
254
240
  password: {
255
- validate: (value) =>
256
- value && value.length < 8 ? "Password too short" : undefined,
257
- },
258
- },
259
- );
241
+ validate: value => (value && value.length < 8 ? 'Password too short' : undefined)
242
+ }
243
+ }
244
+ )
260
245
 
261
246
  return (
262
- <form onSubmit={form.handleSubmit((values) => console.log(values))}>
247
+ <form onSubmit={form.handleSubmit(values => console.log(values))}>
263
248
  <StoreFormInputField
264
249
  state={form.email}
265
250
  type="email"
@@ -273,54 +258,51 @@ function LoginPage() {
273
258
  />
274
259
  <button type="submit">Sign in</button>
275
260
  </form>
276
- );
261
+ )
277
262
  }
278
263
  ```
279
264
 
280
265
  ### 5) Mixed read model for unified UI flags
281
266
 
282
267
  ```tsx
283
- import { createMixedState, createStore } from "juststore";
268
+ import { createMixedState, createStore } from 'juststore'
284
269
 
285
270
  type OpsState = {
286
- syncingConfig: boolean;
287
- savingRoute: boolean;
288
- reloadingAgent: boolean;
289
- };
271
+ syncingConfig: boolean
272
+ savingRoute: boolean
273
+ reloadingAgent: boolean
274
+ }
290
275
 
291
- const opsStore = createStore<OpsState>("ops", {
276
+ const opsStore = createStore<OpsState>('ops', {
292
277
  syncingConfig: false,
293
278
  savingRoute: false,
294
- reloadingAgent: false,
295
- });
279
+ reloadingAgent: false
280
+ })
296
281
 
297
282
  const busyState = createMixedState(
298
283
  opsStore.syncingConfig,
299
284
  opsStore.savingRoute,
300
- opsStore.reloadingAgent,
301
- );
285
+ opsStore.reloadingAgent
286
+ )
302
287
 
303
288
  function GlobalBusyOverlay() {
304
289
  const isBusy = busyState.useCompute(
305
- ([syncingConfig, savingRoute, reloadingAgent]) =>
306
- syncingConfig || savingRoute || reloadingAgent,
307
- );
290
+ ([syncingConfig, savingRoute, reloadingAgent]) => syncingConfig || savingRoute || reloadingAgent
291
+ )
308
292
 
309
- if (!isBusy) return null;
310
- return <div className="overlay">Loading...</div>;
293
+ if (!isBusy) return null
294
+ return <div className="overlay">Loading...</div>
311
295
  }
312
296
 
313
297
  function BusyLabel() {
314
- const label = busyState.useCompute(
315
- ([syncingConfig, savingRoute, reloadingAgent]) => {
316
- if (syncingConfig) return "Syncing config...";
317
- if (savingRoute) return "Saving route...";
318
- if (reloadingAgent) return "Reloading agent...";
319
- return "Idle";
320
- },
321
- );
322
-
323
- return <span>{label}</span>;
298
+ const label = busyState.useCompute(([syncingConfig, savingRoute, reloadingAgent]) => {
299
+ if (syncingConfig) return 'Syncing config...'
300
+ if (savingRoute) return 'Saving route...'
301
+ if (reloadingAgent) return 'Reloading agent...'
302
+ return 'Idle'
303
+ })
304
+
305
+ return <span>{label}</span>
324
306
  }
325
307
  ```
326
308
 
@@ -329,64 +311,59 @@ function BusyLabel() {
329
311
  ### Read and write state
330
312
 
331
313
  ```tsx
332
- const name = store.user.name.use(); // subscribe
333
- const current = store.user.name.value; // read without subscribe
334
- store.user.name.set("Alice");
335
- store.user.name.set((prev) => prev.toUpperCase());
314
+ const name = store.user.name.use() // subscribe
315
+ const current = store.user.name.value // read without subscribe
316
+ store.user.name.set('Alice')
317
+ store.user.name.set(prev => prev.toUpperCase())
336
318
  ```
337
319
 
338
320
  ### Path-based dynamic API
339
321
 
340
322
  ```tsx
341
- store.set("user.name", "Alice");
342
- const name = store.use("user.name");
343
- const value = store.value("user.name");
323
+ store.set('user.name', 'Alice')
324
+ const name = store.use('user.name')
325
+ const value = store.value('user.name')
344
326
  ```
345
327
 
346
328
  ### Arrays
347
329
 
348
330
  ```tsx
349
- store.todos.push({ id: Date.now(), text: "new", done: false });
350
- store.todos.at(0).done.set(true);
331
+ store.todos.push({ id: Date.now(), text: 'new', done: false })
332
+ store.todos.at(0).done.set(true)
351
333
  store.todos.sortedInsert((a, b) => a.id - b.id, {
352
334
  id: 2,
353
- text: "x",
354
- done: false,
355
- });
335
+ text: 'x',
336
+ done: false
337
+ })
356
338
 
357
- const len = store.todos.length;
358
- const liveLen = store.todos.useLength();
339
+ const len = store.todos.length
340
+ const liveLen = store.todos.useLength()
359
341
  ```
360
342
 
361
343
  ### Computed and derived values
362
344
 
363
345
  ```tsx
364
346
  const total = store.cart.items.useCompute(
365
- (items) => items?.reduce((sum, item) => sum + item.price * item.qty, 0) ?? 0,
366
- );
347
+ items => items?.reduce((sum, item) => sum + item.price * item.qty, 0) ?? 0
348
+ )
367
349
 
368
350
  const fahrenheit = store.temperature.derived({
369
- from: (celsius) => ((celsius ?? 0) * 9) / 5 + 32,
370
- to: (f) => ((f - 32) * 5) / 9,
371
- });
351
+ from: celsius => ((celsius ?? 0) * 9) / 5 + 32,
352
+ to: f => ((f - 32) * 5) / 9
353
+ })
372
354
  ```
373
355
 
374
356
  ### Render helpers
375
357
 
376
358
  ```tsx
377
- import { Conditional, Render, RenderWithUpdate } from "juststore";
378
-
379
- <Render state={store.counter}>{(value) => <span>{value}</span>}</Render>;
380
-
381
- <RenderWithUpdate state={store.counter}>
382
- {(value, update) => (
383
- <button onClick={() => update((value ?? 0) + 1)}>{value}</button>
384
- )}
385
- </RenderWithUpdate>;
386
-
387
- <Conditional state={store.user.role} on={(role) => role === "admin"}>
359
+ import { Conditional, Render, RenderWithUpdate } from 'juststore'
360
+ ;<Render state={store.counter}>{value => <span>{value}</span>}</Render>
361
+ ;<RenderWithUpdate state={store.counter}>
362
+ {(value, update) => <button onClick={() => update((value ?? 0) + 1)}>{value}</button>}
363
+ </RenderWithUpdate>
364
+ ;<Conditional state={store.user.role} on={role => role === 'admin'}>
388
365
  <AdminPage />
389
- </Conditional>;
366
+ </Conditional>
390
367
  ```
391
368
 
392
369
  ## API Reference
package/dist/atom.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { createAtom, type Atom };
1
+ export { type Atom, createAtom };
2
2
  /**
3
3
  * An atom is a value that can be subscribed to and updated.
4
4
  *
package/dist/form.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { FieldPath, FieldPathValue, FieldValues, IsEqual } from './path';
2
2
  import type { ArrayProxy, DerivedStateProps, ObjectMutationMethods, Prettify, ValueState } from './types';
3
- export { createForm, useForm, type CreateFormOptions, type DeepNonNullable, type FormArrayState, type FormObjectState, type FormState, type FormStore, type FormValueState };
3
+ export { type CreateFormOptions, createForm, type DeepNonNullable, type FormArrayState, type FormObjectState, type FormState, type FormStore, type FormValueState, useForm };
4
4
  /**
5
5
  * Common form field methods available on every form state node.
6
6
  */
package/dist/form.js CHANGED
@@ -46,7 +46,9 @@ function useForm(defaultValue, fieldConfigs = {}) {
46
46
  function createForm(namespace, defaultValue, fieldConfigs = {}) {
47
47
  const errorNamespace = `_juststore_form_errors.${namespace}`;
48
48
  const errorStore = createStoreRoot(errorNamespace, {}, { memoryOnly: true });
49
- const storeApi = createStoreRoot(namespace, defaultValue, { memoryOnly: true });
49
+ const storeApi = createStoreRoot(namespace, defaultValue, {
50
+ memoryOnly: true
51
+ });
50
52
  const formApi = {
51
53
  clearErrors: () => produce(errorNamespace, undefined, false, true),
52
54
  handleSubmit: (onSubmit) => (e) => {
package/dist/impl.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { FieldPath, FieldPathValue, FieldValues } from './path';
2
2
  import { getStableKeys, setExternalKeyOrder } from './stable_keys';
3
- export { getNestedValue, getSnapshot, getStableKeys, isClass, isEqual, isRecord, joinPath, notifyListeners, produce, rename, setExternalKeyOrder, setLeaf, setNestedValue, subscribe, testReset, updateSnapshot, useDebounce, useObject, useCompute };
3
+ export { getNestedValue, getSnapshot, getStableKeys, isClass, isEqual, isRecord, joinPath, notifyListeners, produce, rename, setExternalKeyOrder, setLeaf, setNestedValue, subscribe, testReset, updateSnapshot, useCompute, useDebounce, useObject };
4
4
  declare function testReset(): void;
5
5
  declare function isClass(value: unknown): boolean;
6
6
  declare function isRecord(value: unknown): boolean;
package/dist/impl.js CHANGED
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from '
2
2
  import rfcIsEqual from 'react-fast-compare';
3
3
  import { KVStore } from './kv_store';
4
4
  import { getExternalKeyOrder, getStableKeys, setExternalKeyOrder } from './stable_keys';
5
- export { getNestedValue, getSnapshot, getStableKeys, isClass, isEqual, isRecord, joinPath, notifyListeners, produce, rename, setExternalKeyOrder, setLeaf, setNestedValue, subscribe, testReset, updateSnapshot, useDebounce, useObject, useCompute };
5
+ export { getNestedValue, getSnapshot, getStableKeys, isClass, isEqual, isRecord, joinPath, notifyListeners, produce, rename, setExternalKeyOrder, setLeaf, setNestedValue, subscribe, testReset, updateSnapshot, useCompute, useDebounce, useObject };
6
6
  const inMemStorage = new Map();
7
7
  const listeners = new Map();
8
8
  const descendantListenerKeysByPrefix = new Map();
@@ -1,5 +1,5 @@
1
1
  import { getNestedValue, setNestedValue } from './impl';
2
- export { getNestedValue, KVStore, setNestedValue, type KeyValueStore };
2
+ export { getNestedValue, type KeyValueStore, KVStore, setNestedValue };
3
3
  type KeyValueStore = {
4
4
  getBroadcastChannel: () => BroadcastChannel | undefined;
5
5
  setBroadcastChannel: (broadcastChannel: BroadcastChannel) => void;
package/dist/kv_store.js CHANGED
@@ -57,7 +57,11 @@ class KVStore {
57
57
  localStorageSet(rootKey, rootValue);
58
58
  // Broadcast change to other tabs
59
59
  if (this.broadcastChannel) {
60
- this.broadcastChannel.postMessage({ type: 'set', key: rootKey, value: rootValue });
60
+ this.broadcastChannel.postMessage({
61
+ type: 'set',
62
+ key: rootKey,
63
+ value: rootValue
64
+ });
61
65
  }
62
66
  }
63
67
  }
@@ -82,7 +86,11 @@ class KVStore {
82
86
  if (!this.memoryOnly && typeof window !== 'undefined') {
83
87
  localStorageSet(rootKey, updatedRoot);
84
88
  if (this.broadcastChannel) {
85
- this.broadcastChannel.postMessage({ type: 'set', key: rootKey, value: updatedRoot });
89
+ this.broadcastChannel.postMessage({
90
+ type: 'set',
91
+ key: rootKey,
92
+ value: updatedRoot
93
+ });
86
94
  }
87
95
  }
88
96
  }
package/dist/memory.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { FieldValues } from './path';
2
2
  import type { State, ValueState } from './types';
3
- export { createMemoryStore, useMemoryStore, type MemoryStore };
3
+ export { createMemoryStore, type MemoryStore, useMemoryStore };
4
4
  /**
5
5
  * A component local store with React bindings.
6
6
  *
@@ -0,0 +1,45 @@
1
+ export { type Atom, createAtom };
2
+ /**
3
+ * An atom is a value that can be subscribed to and updated.
4
+ *
5
+ * @param T - The type of the value
6
+ * @returns The atom
7
+ */
8
+ type Atom<T> = {
9
+ /** The current value. */
10
+ readonly value: T;
11
+ /** Subscribe to the value. */
12
+ use: () => T;
13
+ /** Set the value. */
14
+ set: AtomSetState<T>;
15
+ /** Reset the value to the default value. */
16
+ reset: () => void;
17
+ /** Subscribe to the value.with a callback function. */
18
+ subscribe: (listener: (value: T) => void) => () => void;
19
+ /** Compute a derived value from the current value, similar to useState + useMemo */
20
+ useCompute: <R>(fn: (value: T) => R, deps?: readonly unknown[]) => R;
21
+ };
22
+ type AtomSetState<T> = (value: T | ((prev: T) => T)) => void;
23
+ /**
24
+ * Creates an atom with a given id and default value.
25
+ *
26
+ * @param id - The id of the atom
27
+ * @param defaultValue - The default value of the atom
28
+ * @returns The atom
29
+ * @example
30
+ * const stateA = createAtom(useId(), false)
31
+ * return (
32
+ * <>
33
+ * <ComponentA/>
34
+ * <ComponentB/>
35
+ * <Render state={stateA}>
36
+ * {(value, setValue) => (
37
+ * <button onClick={() => setValue(!value)}>{value ? 'Hide' : 'Show'}</button>
38
+ * )}
39
+ * </Render>
40
+ * <ComponentC/>
41
+ * <ComponentD/>
42
+ * </>
43
+ * )
44
+ */
45
+ declare function createAtom<T>(id: string, defaultValue: T, persistent?: boolean): Atom<T>;