mono-jsx 0.4.0 → 0.5.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 +224 -121
- package/jsx-runtime.mjs +587 -534
- package/package.json +1 -1
- package/setup.mjs +1 -0
- package/types/html.d.ts +20 -11
- package/types/jsx.d.ts +11 -5
- package/types/mono.d.ts +31 -20
- package/types/render.d.ts +2 -2
package/README.md
CHANGED
|
@@ -6,8 +6,8 @@ mono-jsx is a JSX runtime that renders `<html>` element to `Response` object in
|
|
|
6
6
|
|
|
7
7
|
- 🚀 No build step needed
|
|
8
8
|
- 🦋 Lightweight (8KB gzipped), zero dependencies
|
|
9
|
-
-
|
|
10
|
-
-
|
|
9
|
+
- 🚦 Signals as reactive primitives
|
|
10
|
+
- 🗂️ Complete Web API TypeScript definitions
|
|
11
11
|
- ⏳ Streaming rendering
|
|
12
12
|
- 🥷 [htmx](#using-htmx) integration
|
|
13
13
|
- 🌎 Universal, works in Node.js, Deno, Bun, Cloudflare Workers, etc.
|
|
@@ -72,8 +72,8 @@ export default {
|
|
|
72
72
|
<html>
|
|
73
73
|
<h1>Welcome to mono-jsx!</h1>
|
|
74
74
|
</html>
|
|
75
|
-
)
|
|
76
|
-
}
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
For Deno/Bun users, you can run the `app.tsx` directly:
|
|
@@ -173,7 +173,7 @@ function Container() {
|
|
|
173
173
|
{/* Named slot */}
|
|
174
174
|
<slot name="desc" />
|
|
175
175
|
</div>
|
|
176
|
-
)
|
|
176
|
+
)
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
function App() {
|
|
@@ -184,7 +184,7 @@ function App() {
|
|
|
184
184
|
{/* This goes to the default slot */}
|
|
185
185
|
<h1>Hello world!</h1>
|
|
186
186
|
</Container>
|
|
187
|
-
)
|
|
187
|
+
)
|
|
188
188
|
}
|
|
189
189
|
```
|
|
190
190
|
|
|
@@ -207,7 +207,7 @@ function App() {
|
|
|
207
207
|
<style>{css`h1 { font-size: 3rem; }`}</style>
|
|
208
208
|
<script>{js`console.log("Hello world!")`}</script>
|
|
209
209
|
</head>
|
|
210
|
-
)
|
|
210
|
+
)
|
|
211
211
|
}
|
|
212
212
|
```
|
|
213
213
|
|
|
@@ -224,7 +224,7 @@ function Button() {
|
|
|
224
224
|
<button onClick={(evt) => alert("BOOM!")}>
|
|
225
225
|
Click Me
|
|
226
226
|
</button>
|
|
227
|
-
)
|
|
227
|
+
)
|
|
228
228
|
}
|
|
229
229
|
```
|
|
230
230
|
|
|
@@ -234,9 +234,10 @@ function Button() {
|
|
|
234
234
|
```tsx
|
|
235
235
|
import { doSomething } from "some-library";
|
|
236
236
|
|
|
237
|
-
function Button(this: FC
|
|
238
|
-
|
|
239
|
-
|
|
237
|
+
function Button(this: FC<{ count: 0 }>, props: { role: string }) {
|
|
238
|
+
const message = "BOOM!"; // server-side variable
|
|
239
|
+
this.count = 0; // initialize a signal
|
|
240
|
+
console.log(message); // only prints on server-side
|
|
240
241
|
return (
|
|
241
242
|
<button
|
|
242
243
|
role={props.role}
|
|
@@ -247,37 +248,27 @@ function Button(this: FC, props: { role: string }) {
|
|
|
247
248
|
Deno.exit(0); // ❌ `Deno` is unavailable in the browser
|
|
248
249
|
document.title = "BOOM!"; // ✅ `document` is a browser API
|
|
249
250
|
console.log(evt.target); // ✅ `evt` is the event object
|
|
250
|
-
this.count++; // ✅ update the
|
|
251
|
+
this.count++; // ✅ update the `count` signal
|
|
251
252
|
}}
|
|
252
253
|
>
|
|
253
|
-
|
|
254
|
+
<slot />
|
|
254
255
|
</button>
|
|
255
|
-
)
|
|
256
|
+
)
|
|
256
257
|
}
|
|
257
258
|
```
|
|
258
259
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
```jsx
|
|
262
|
-
function App() {
|
|
263
|
-
return (
|
|
264
|
-
<div onMount={(evt) => console.log(evt.target, "Mounted!")}>
|
|
265
|
-
<h1>Welcome to mono-jsx!</h1>
|
|
266
|
-
</div>
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
```
|
|
260
|
+
### Using `<form>` Element
|
|
270
261
|
|
|
271
|
-
mono-jsx
|
|
262
|
+
mono-jsx supports `<form>` elements with the `action` attribute. The `action` attribute can be a string URL or a function that accepts a `FormData` object. The function will be called on form submission, and the `FormData` object will contain the form data.
|
|
272
263
|
|
|
273
264
|
```tsx
|
|
274
265
|
function App() {
|
|
275
266
|
return (
|
|
276
|
-
<form action={(data: FormData
|
|
267
|
+
<form action={(data: FormData) => console.log(data.get("name"))}>
|
|
277
268
|
<input type="text" name="name" />
|
|
278
269
|
<button type="submit">Submit</button>
|
|
279
270
|
</form>
|
|
280
|
-
)
|
|
271
|
+
)
|
|
281
272
|
}
|
|
282
273
|
```
|
|
283
274
|
|
|
@@ -291,13 +282,13 @@ async function Loader(props: { url: string }) {
|
|
|
291
282
|
return <JsonViewer data={data} />;
|
|
292
283
|
}
|
|
293
284
|
|
|
294
|
-
default {
|
|
285
|
+
export default {
|
|
295
286
|
fetch: (req) => (
|
|
296
287
|
<html>
|
|
297
288
|
<Loader url="https://api.example.com/data" placeholder={<p>Loading...</p>} />
|
|
298
289
|
</html>
|
|
299
|
-
)
|
|
300
|
-
}
|
|
290
|
+
)
|
|
291
|
+
}
|
|
301
292
|
```
|
|
302
293
|
|
|
303
294
|
You can also use async generators to yield multiple elements over time. This is useful for streaming rendering of LLM tokens:
|
|
@@ -318,113 +309,169 @@ async function* Chat(props: { prompt: string }) {
|
|
|
318
309
|
}
|
|
319
310
|
}
|
|
320
311
|
|
|
321
|
-
|
|
322
|
-
default {
|
|
312
|
+
export default {
|
|
323
313
|
fetch: (req) => (
|
|
324
314
|
<html>
|
|
325
315
|
<Chat prompt="Tell me a story" placeholder={<span style="color:grey">●</span>} />
|
|
326
316
|
</html>
|
|
327
|
-
)
|
|
328
|
-
}
|
|
317
|
+
)
|
|
318
|
+
}
|
|
329
319
|
```
|
|
330
320
|
|
|
331
|
-
##
|
|
321
|
+
## Using Signals
|
|
332
322
|
|
|
333
|
-
mono-jsx
|
|
323
|
+
mono-jsx uses signals for updating the view when a signal changes. Signals are similar to React's state, but they are more lightweight and efficient. You can use signals to manage state in your components.
|
|
334
324
|
|
|
335
|
-
### Using Component
|
|
325
|
+
### Using Component Signals
|
|
336
326
|
|
|
337
|
-
You can use the `this` keyword in your components to manage
|
|
327
|
+
You can use the `this` keyword in your components to manage signals. The signals is bound to the component instance and can be updated directly, and will automatically re-render the view when a signal changes:
|
|
338
328
|
|
|
339
329
|
```tsx
|
|
340
330
|
function Counter(
|
|
341
331
|
this: FC<{ count: number }>,
|
|
342
332
|
props: { initialCount?: number },
|
|
343
333
|
) {
|
|
344
|
-
// Initialize
|
|
334
|
+
// Initialize a singal
|
|
345
335
|
this.count = props.initialCount ?? 0;
|
|
346
336
|
|
|
347
337
|
return (
|
|
348
338
|
<div>
|
|
349
|
-
{/* render
|
|
339
|
+
{/* render singal */}
|
|
350
340
|
<span>{this.count}</span>
|
|
351
341
|
|
|
352
|
-
{/* Update
|
|
342
|
+
{/* Update singal to trigger re-render */}
|
|
353
343
|
<button onClick={() => this.count--}>-</button>
|
|
354
344
|
<button onClick={() => this.count++}>+</button>
|
|
355
345
|
</div>
|
|
356
|
-
)
|
|
346
|
+
)
|
|
357
347
|
}
|
|
358
348
|
```
|
|
359
349
|
|
|
360
|
-
### Using App
|
|
350
|
+
### Using App Signals
|
|
361
351
|
|
|
362
|
-
You can define app
|
|
352
|
+
You can define app signals by adding `app` prop to the root `<html>` element. The app signals is available in all components via `this.app.<SignalName>`. Changes to the app signals will trigger re-renders in all components that use it:
|
|
363
353
|
|
|
364
354
|
```tsx
|
|
365
|
-
|
|
355
|
+
interface AppSignals {
|
|
356
|
+
themeColor: string;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function Header(this: FC<{}, AppSignals>) {
|
|
366
360
|
return (
|
|
367
361
|
<header>
|
|
368
|
-
<h1
|
|
362
|
+
<h1 style={this.computed(() => ({ color: this.app.themeColor }))}>Welcome to mono-jsx!</h1>
|
|
369
363
|
</header>
|
|
370
|
-
)
|
|
364
|
+
)
|
|
371
365
|
}
|
|
372
366
|
|
|
373
|
-
function Footer(this: FC<{},
|
|
367
|
+
function Footer(this: FC<{}, AppSignals>) {
|
|
374
368
|
return (
|
|
375
369
|
<footer>
|
|
376
|
-
<p
|
|
370
|
+
<p style={this.computed(() => ({ color: this.app.themeColor }))}>(c) 2025 mono-jsx.</p>
|
|
377
371
|
</footer>
|
|
378
|
-
)
|
|
372
|
+
)
|
|
379
373
|
}
|
|
380
374
|
|
|
381
|
-
function Main(this: FC<{},
|
|
375
|
+
function Main(this: FC<{}, AppSignals>) {
|
|
382
376
|
return (
|
|
383
377
|
<main>
|
|
384
|
-
<
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
placeholder="Enter a new title"
|
|
389
|
-
/>
|
|
378
|
+
<p>
|
|
379
|
+
<label>Theme Color: </label>
|
|
380
|
+
<input type="color" onInput={({ target }) => this.app.themeColor = target.value}/>
|
|
381
|
+
</p>
|
|
390
382
|
</main>
|
|
391
|
-
)
|
|
383
|
+
)
|
|
392
384
|
}
|
|
393
385
|
|
|
394
386
|
export default {
|
|
395
387
|
fetch: (req) => (
|
|
396
|
-
<html
|
|
388
|
+
<html app={{ themeColor: "#232323" }}>
|
|
397
389
|
<Header />
|
|
398
390
|
<Main />
|
|
399
391
|
<Footer />
|
|
400
392
|
</html>
|
|
401
|
-
)
|
|
402
|
-
}
|
|
393
|
+
)
|
|
394
|
+
}
|
|
403
395
|
```
|
|
404
396
|
|
|
405
|
-
### Using Computed
|
|
397
|
+
### Using Computed Signals
|
|
406
398
|
|
|
407
|
-
You can use `this.computed` to create
|
|
399
|
+
You can use `this.computed` to create a derived signal based on other signals:
|
|
408
400
|
|
|
409
401
|
```tsx
|
|
410
402
|
function App(this: FC<{ input: string }>) {
|
|
411
|
-
this.input = "Welcome to mono-jsx
|
|
403
|
+
this.input = "Welcome to mono-jsx";
|
|
412
404
|
return (
|
|
413
405
|
<div>
|
|
414
406
|
<h1>{this.computed(() => this.input + "!")}</h1>
|
|
415
407
|
|
|
416
408
|
<form action={(fd) => this.input = fd.get("input") as string}>
|
|
417
|
-
<input type="text" name="input" value={this.input} />
|
|
409
|
+
<input type="text" name="input" value={"" + this.input} />
|
|
418
410
|
<button type="submit">Submit</button>
|
|
419
411
|
</form>
|
|
420
412
|
</div>
|
|
421
|
-
)
|
|
413
|
+
)
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Using Effects
|
|
418
|
+
|
|
419
|
+
You can use `this.effect` to create side effects based on signals. The effect will run whenever the signal changes:
|
|
420
|
+
|
|
421
|
+
```tsx
|
|
422
|
+
function App(this: FC<{ count: number }>) {
|
|
423
|
+
this.count = 0;
|
|
424
|
+
|
|
425
|
+
this.effect(() => {
|
|
426
|
+
console.log("Count changed:", this.count);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
return (
|
|
430
|
+
<div>
|
|
431
|
+
<span>{this.count}</span>
|
|
432
|
+
<button onClick={() => this.count++}>+</button>
|
|
433
|
+
</div>
|
|
434
|
+
)
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
The callback function of `this.effect` can return a cleanup function that gets run once the component element has been removed via `<toggle>` or `<switch>` condition rendering:
|
|
439
|
+
|
|
440
|
+
```tsx
|
|
441
|
+
function Counter(this: FC<{ count: number }>) {
|
|
442
|
+
this.count = 0;
|
|
443
|
+
|
|
444
|
+
this.effect(() => {
|
|
445
|
+
const interval = setInterval(() => {
|
|
446
|
+
this.count++;
|
|
447
|
+
}, 1000);
|
|
448
|
+
|
|
449
|
+
return () => clearInterval(interval);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
return (
|
|
453
|
+
<div>
|
|
454
|
+
<span>{this.count}</span>
|
|
455
|
+
</div>
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function App(this: FC<{ show: boolean }>) {
|
|
460
|
+
this.show = true
|
|
461
|
+
return (
|
|
462
|
+
<div>
|
|
463
|
+
<toggle show={this.show}>
|
|
464
|
+
<Foo />
|
|
465
|
+
</toggle>
|
|
466
|
+
<button onClick={e => this.show = !this.show }>{this.computed(() => this.show ? 'Hide': 'Show')}</button>
|
|
467
|
+
</div>
|
|
468
|
+
)
|
|
422
469
|
}
|
|
423
470
|
```
|
|
424
471
|
|
|
425
|
-
### Using `<toggle>` Element with
|
|
472
|
+
### Using `<toggle>` Element with Signals
|
|
426
473
|
|
|
427
|
-
The `<toggle>` element conditionally renders content based on the
|
|
474
|
+
The `<toggle>` element conditionally renders content based on the `show` prop:
|
|
428
475
|
|
|
429
476
|
```tsx
|
|
430
477
|
function App(this: FC<{ show: boolean }>) {
|
|
@@ -436,7 +483,7 @@ function App(this: FC<{ show: boolean }>) {
|
|
|
436
483
|
|
|
437
484
|
return (
|
|
438
485
|
<div>
|
|
439
|
-
<toggle
|
|
486
|
+
<toggle show={this.show}>
|
|
440
487
|
<h1>Welcome to mono-jsx!</h1>
|
|
441
488
|
</toggle>
|
|
442
489
|
|
|
@@ -444,16 +491,16 @@ function App(this: FC<{ show: boolean }>) {
|
|
|
444
491
|
{this.computed(() => this.show ? "Hide" : "Show")}
|
|
445
492
|
</button>
|
|
446
493
|
</div>
|
|
447
|
-
)
|
|
494
|
+
)
|
|
448
495
|
}
|
|
449
496
|
```
|
|
450
497
|
|
|
451
|
-
### Using `<switch>` Element with
|
|
498
|
+
### Using `<switch>` Element with Signals
|
|
452
499
|
|
|
453
|
-
The `<switch>` element renders different content based on the value of a
|
|
500
|
+
The `<switch>` element renders different content based on the value of a signal. Elements with matching `slot` attributes are displayed when their value matches, otherwise default slots are shown:
|
|
454
501
|
|
|
455
502
|
```tsx
|
|
456
|
-
function App(this: FC<{ lang: "en" | "zh" | "
|
|
503
|
+
function App(this: FC<{ lang: "en" | "zh" | "🙂" }>) {
|
|
457
504
|
this.lang = "en";
|
|
458
505
|
|
|
459
506
|
return (
|
|
@@ -466,19 +513,19 @@ function App(this: FC<{ lang: "en" | "zh" | "emoji" }>) {
|
|
|
466
513
|
<p>
|
|
467
514
|
<button onClick={() => this.lang = "en"}>English</button>
|
|
468
515
|
<button onClick={() => this.lang = "zh"}>中文</button>
|
|
469
|
-
<button onClick={() => this.lang = "
|
|
516
|
+
<button onClick={() => this.lang = "🙂"}>🙂</button>
|
|
470
517
|
</p>
|
|
471
518
|
</div>
|
|
472
|
-
)
|
|
519
|
+
)
|
|
473
520
|
}
|
|
474
521
|
```
|
|
475
522
|
|
|
476
|
-
### Limitation of
|
|
523
|
+
### Limitation of Signals
|
|
477
524
|
|
|
478
|
-
1\.
|
|
525
|
+
1\. Arrow function are non-stateful components.
|
|
479
526
|
|
|
480
527
|
```tsx
|
|
481
|
-
// ❌ Won't work -
|
|
528
|
+
// ❌ Won't work - use `this` in a non-stateful component
|
|
482
529
|
const App = () => {
|
|
483
530
|
this.count = 0;
|
|
484
531
|
return (
|
|
@@ -486,7 +533,7 @@ const App = () => {
|
|
|
486
533
|
<span>{this.count}</span>
|
|
487
534
|
<button onClick={() => this.count++}>+</button>
|
|
488
535
|
</div>
|
|
489
|
-
)
|
|
536
|
+
)
|
|
490
537
|
};
|
|
491
538
|
|
|
492
539
|
// ✅ Works correctly
|
|
@@ -497,16 +544,16 @@ function App(this: FC) {
|
|
|
497
544
|
<span>{this.count}</span>
|
|
498
545
|
<button onClick={() => this.count++}>+</button>
|
|
499
546
|
</div>
|
|
500
|
-
)
|
|
547
|
+
)
|
|
501
548
|
}
|
|
502
549
|
```
|
|
503
550
|
|
|
504
|
-
2\.
|
|
551
|
+
2\. Signals cannot be computed outside of the `this.computed` method.
|
|
505
552
|
|
|
506
553
|
```tsx
|
|
507
|
-
// ❌ Won't work -
|
|
554
|
+
// ❌ Won't work - updates of a signal won't refresh the view
|
|
508
555
|
function App(this: FC<{ message: string }>) {
|
|
509
|
-
this.message = "Welcome to mono-jsx
|
|
556
|
+
this.message = "Welcome to mono-jsx";
|
|
510
557
|
return (
|
|
511
558
|
<div>
|
|
512
559
|
<h1 title={this.message + "!"}>{this.message + "!"}</h1>
|
|
@@ -514,12 +561,12 @@ function App(this: FC<{ message: string }>) {
|
|
|
514
561
|
Click Me
|
|
515
562
|
</button>
|
|
516
563
|
</div>
|
|
517
|
-
)
|
|
564
|
+
)
|
|
518
565
|
}
|
|
519
566
|
|
|
520
567
|
// ✅ Works correctly
|
|
521
568
|
function App(this: FC) {
|
|
522
|
-
this.message = "Welcome to mono-jsx
|
|
569
|
+
this.message = "Welcome to mono-jsx";
|
|
523
570
|
return (
|
|
524
571
|
<div>
|
|
525
572
|
<h1 title={this.computed(() => this.message + "!")}>{this.computed(() => this.message + "!")}</h1>
|
|
@@ -527,33 +574,89 @@ function App(this: FC) {
|
|
|
527
574
|
Click Me
|
|
528
575
|
</button>
|
|
529
576
|
</div>
|
|
530
|
-
)
|
|
577
|
+
)
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
3\. The callback function of `this.computed` must be a pure function. That means it should not create side effects or access any non-stateful variables. For example, you cannot use `Deno` or `document` in the callback function:
|
|
582
|
+
|
|
583
|
+
```tsx
|
|
584
|
+
// ❌ Won't work - throws `Deno is not defined` when the button is clicked
|
|
585
|
+
function App(this: FC<{ message: string }>) {
|
|
586
|
+
this.message = "Welcome to mono-jsx";
|
|
587
|
+
return (
|
|
588
|
+
<div>
|
|
589
|
+
<h1>{this.computed(() => this.message + "! (Deno " + Deno.version.deno + ")")}</h1>
|
|
590
|
+
<button onClick={() => this.message = "Clicked"}>
|
|
591
|
+
Click Me
|
|
592
|
+
</button>
|
|
593
|
+
</div>
|
|
594
|
+
)
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// ✅ Works correctly
|
|
598
|
+
function App(this: FC<{ message: string, denoVersion: string }>) {
|
|
599
|
+
this.denoVersion = Deno.version.deno;
|
|
600
|
+
this.message = "Welcome to mono-jsx";
|
|
601
|
+
return (
|
|
602
|
+
<div>
|
|
603
|
+
<h1>{this.computed(() => this.message + "! (Deno " + this.denoVersion + ")")}</h1>
|
|
604
|
+
<button onClick={() => this.message = "Clicked"}>
|
|
605
|
+
Click Me
|
|
606
|
+
</button>
|
|
607
|
+
</div>
|
|
608
|
+
)
|
|
531
609
|
}
|
|
532
610
|
```
|
|
533
611
|
|
|
534
612
|
## Using `this` in Components
|
|
535
613
|
|
|
536
|
-
mono-jsx binds a
|
|
614
|
+
mono-jsx binds a scoped signals object to `this` of your component functions. This allows you to access signals, context, and request information directly in your components.
|
|
537
615
|
|
|
538
|
-
The `this` object
|
|
616
|
+
The `this` object has the following built-in properties:
|
|
539
617
|
|
|
540
|
-
- `app`: The app
|
|
618
|
+
- `app`: The app signals defined on the root `<html>` element.
|
|
541
619
|
- `context`: The context defined on the root `<html>` element.
|
|
542
620
|
- `request`: The request object from the `fetch` handler.
|
|
543
|
-
- `
|
|
621
|
+
- `refs`: A map of refs defined in the component.
|
|
622
|
+
- `computed`: A method to create a computed signal.
|
|
623
|
+
- `effect`: A method to create side effects.
|
|
544
624
|
|
|
545
625
|
```ts
|
|
546
|
-
type FC<
|
|
547
|
-
readonly app:
|
|
626
|
+
type FC<Signals = {}, AppSignals = {}, Context = {}> = {
|
|
627
|
+
readonly app: AppSignals;
|
|
548
628
|
readonly context: Context;
|
|
549
629
|
readonly request: Request;
|
|
550
|
-
readonly
|
|
551
|
-
|
|
630
|
+
readonly refs: Record<string, HTMLElement | null>;
|
|
631
|
+
readonly computed: <T = unknown>(fn: () => T) => T;
|
|
632
|
+
readonly effect: (fn: () => void | (() => void)) => void;
|
|
633
|
+
} & Omit<Signals, "app" | "context" | "request" | "computed" | "effect">;
|
|
552
634
|
```
|
|
553
635
|
|
|
554
|
-
### Using
|
|
636
|
+
### Using Signals
|
|
637
|
+
|
|
638
|
+
See the [Using Signals](#using-signals) section for more details on how to use signals in your components.
|
|
639
|
+
|
|
640
|
+
### Using Refs
|
|
555
641
|
|
|
556
|
-
|
|
642
|
+
You can use `this.refs` to access refs in your components. Define refs in your component using the `ref` attribute:
|
|
643
|
+
|
|
644
|
+
```tsx
|
|
645
|
+
function App(this: FC) {
|
|
646
|
+
this.effect(() => {
|
|
647
|
+
this.refs.input?.addEventListener("input", (evt) => {
|
|
648
|
+
console.log("Input changed:", evt.target.value);
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
return (
|
|
653
|
+
<div>
|
|
654
|
+
<input ref={this.refs.input} type="text" />
|
|
655
|
+
<button onClick={() => this.refs.input?.focus()}>Focus</button>
|
|
656
|
+
</div>
|
|
657
|
+
)
|
|
658
|
+
}
|
|
659
|
+
```
|
|
557
660
|
|
|
558
661
|
### Using Context
|
|
559
662
|
|
|
@@ -567,7 +670,7 @@ function Dash(this: FC<{}, {}, { auth: { uuid: string; name: string } }>) {
|
|
|
567
670
|
<h1>Welcome back, {auth.name}!</h1>
|
|
568
671
|
<p>Your UUID is {auth.uuid}</p>
|
|
569
672
|
</div>
|
|
570
|
-
)
|
|
673
|
+
)
|
|
571
674
|
}
|
|
572
675
|
|
|
573
676
|
export default {
|
|
@@ -578,9 +681,9 @@ export default {
|
|
|
578
681
|
{!auth && <p>Please Login</p>}
|
|
579
682
|
{auth && <Dash />}
|
|
580
683
|
</html>
|
|
581
|
-
)
|
|
582
|
-
}
|
|
583
|
-
}
|
|
684
|
+
)
|
|
685
|
+
}
|
|
686
|
+
}
|
|
584
687
|
```
|
|
585
688
|
|
|
586
689
|
### Accessing Request Info
|
|
@@ -597,7 +700,7 @@ function RequestInfo(this: FC) {
|
|
|
597
700
|
<p>{request.url}</p>
|
|
598
701
|
<p>{request.headers.get("user-agent")}</p>
|
|
599
702
|
</div>
|
|
600
|
-
)
|
|
703
|
+
)
|
|
601
704
|
}
|
|
602
705
|
|
|
603
706
|
export default {
|
|
@@ -605,8 +708,8 @@ export default {
|
|
|
605
708
|
<html request={req}>
|
|
606
709
|
<RequestInfo />
|
|
607
710
|
</html>
|
|
608
|
-
)
|
|
609
|
-
}
|
|
711
|
+
)
|
|
712
|
+
}
|
|
610
713
|
```
|
|
611
714
|
|
|
612
715
|
## Streaming Rendering
|
|
@@ -626,8 +729,8 @@ export default {
|
|
|
626
729
|
<p>After 1 second</p>
|
|
627
730
|
</Sleep>
|
|
628
731
|
</html>
|
|
629
|
-
)
|
|
630
|
-
}
|
|
732
|
+
)
|
|
733
|
+
}
|
|
631
734
|
```
|
|
632
735
|
|
|
633
736
|
You can set the `rendering` attribute to `"eager"` to force synchronous rendering (the `placeholder` will be ignored):
|
|
@@ -640,8 +743,8 @@ export default {
|
|
|
640
743
|
<p>After 1 second</p>
|
|
641
744
|
</Sleep>
|
|
642
745
|
</html>
|
|
643
|
-
)
|
|
644
|
-
}
|
|
746
|
+
)
|
|
747
|
+
}
|
|
645
748
|
```
|
|
646
749
|
|
|
647
750
|
You can add the `catch` attribute to handle errors in the async component. The `catch` attribute should be a function that returns a JSX element:
|
|
@@ -657,8 +760,8 @@ export default {
|
|
|
657
760
|
<html>
|
|
658
761
|
<Hello catch={err => <p>{err.message}</p>} />
|
|
659
762
|
</html>
|
|
660
|
-
)
|
|
661
|
-
}
|
|
763
|
+
)
|
|
764
|
+
}
|
|
662
765
|
```
|
|
663
766
|
|
|
664
767
|
## Customizing html Response
|
|
@@ -678,8 +781,8 @@ export default {
|
|
|
678
781
|
>
|
|
679
782
|
<h1>Page Not Found</h1>
|
|
680
783
|
</html>
|
|
681
|
-
)
|
|
682
|
-
}
|
|
784
|
+
)
|
|
785
|
+
}
|
|
683
786
|
```
|
|
684
787
|
|
|
685
788
|
### Using htmx
|
|
@@ -705,9 +808,9 @@ export default {
|
|
|
705
808
|
Click Me
|
|
706
809
|
</button>
|
|
707
810
|
</html>
|
|
708
|
-
)
|
|
709
|
-
}
|
|
710
|
-
}
|
|
811
|
+
)
|
|
812
|
+
}
|
|
813
|
+
}
|
|
711
814
|
```
|
|
712
815
|
|
|
713
816
|
#### Adding htmx Extensions
|
|
@@ -722,8 +825,8 @@ export default {
|
|
|
722
825
|
Click Me
|
|
723
826
|
</button>
|
|
724
827
|
</html>
|
|
725
|
-
)
|
|
726
|
-
}
|
|
828
|
+
)
|
|
829
|
+
}
|
|
727
830
|
```
|
|
728
831
|
|
|
729
832
|
#### Specifying htmx Version
|
|
@@ -738,8 +841,8 @@ export default {
|
|
|
738
841
|
Click Me
|
|
739
842
|
</button>
|
|
740
843
|
</html>
|
|
741
|
-
)
|
|
742
|
-
}
|
|
844
|
+
)
|
|
845
|
+
}
|
|
743
846
|
```
|
|
744
847
|
|
|
745
848
|
#### Installing htmx Manually
|
|
@@ -760,8 +863,8 @@ export default {
|
|
|
760
863
|
</button>
|
|
761
864
|
</body>
|
|
762
865
|
</html>
|
|
763
|
-
)
|
|
764
|
-
}
|
|
866
|
+
)
|
|
867
|
+
}
|
|
765
868
|
```
|
|
766
869
|
|
|
767
870
|
## License
|