Async Components

Estimated reading time: 2 minutes

A Kita Html component can be an async function. When it is, the JSX expression evaluates to a Promise<string> instead of a string.

async function 
function UserCard({ id }: {
    id: string;
}): Promise<string>
UserCard
({
id: string
id
}: {
id: string
id
: string }) {
const
const user: {
    name: string;
}
user
= await
let db: {
    getUser(id: string): Promise<{
        name: string;
    }>;
}
db
.
function getUser(id: string): Promise<{
    name: string;
}>
getUser
(
id: string
id
)
return <
JSX.IntrinsicElements.div: JSX.HtmlTag
div
JSX.HtmlTag.safe?: boolean | undefined

When set to true, all inner content (html or not) of this tag will be escaped when evaluated.

Warning: This will escape even inner jsx tags. You should only use this in the most inner tag of the html tree.

@example
<div>{'<script />'}</div>
'<div><script /></div>'
<div safe>{'<script />'}</div>
'<div><script /></div>'
<div><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'

// Escapes even inner jsx tags
<div safe><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'
@defaultfalse@seehttps ://github.com/kitajs/html/tree/master/packages/html#sanitization
safe
>User card for user: {
const user: {
    name: string;
}
user
.
name: string
name
}</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
} const
const html: JSX.Element
html
= <
function UserCard({ id }: {
    id: string;
}): Promise<string>
UserCard
id: string
id
="123" />
// html is Promise<string>
<div>User card for user: John Doe</div>

Promise propagation

When any child in a component tree is async, the entire parent chain becomes async automatically. The runtime detects promises during child concatenation and wraps the result in Promise.all. No manual promise handling is required at intermediate levels.

function 
function Page(): JSX.Element
Page
() {
return ( <
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
<
function UserCard({ id }: {
    id: string;
}): Promise<string>
UserCard
id: string
id
="123" />
<
JSX.IntrinsicElements.p: JSX.HtmlTag
p
>Static content</
JSX.IntrinsicElements.p: JSX.HtmlTag
p
>
</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
) } const
const html: JSX.Element
html
= <
function Page(): JSX.Element
Page
/>
// Page() returns Promise<string> because UserCard is async
<div>
  <div>User card for id: 123</div>
  <p>Static content</p>
</div>

This propagation is transitive. If UserCard is nested three levels deep, every ancestor up to the root becomes a Promise<string>.

JSX.Element type

The type JSX.Element is defined as string | Promise<string>. This reflects the reality that any component tree may or may not contain async components. When you are certain that a tree is fully synchronous, you can cast the result to string.

// When you know the tree is sync-only
const 
const html: string
html
= (<
function StaticPage(): JSX.Element
StaticPage
/>) as string
<div>Static page content</div>

TypeScript cannot infer whether a component tree is synchronous or asynchronous at the type level, because the presence of async children is a runtime property of the tree structure. This is a known TypeScript limitation (microsoft/TypeScript#14729).

When to await

If you need the resolved HTML string, await the result. If you are passing it to a stream or to a framework integration that handles promises, you can pass the promise directly without awaiting.

// Await when you need the string
const html = await (<Page />)
response.end(html)

// Pass directly when the consumer handles promises, e.g our fastify or express plugins
reply.html(<Page />)