Components & Props

Components are the building blocks of PawaJS applications. They are isomorphic (running on both server and client) and support async operations by default.

Defining Components

A component is a simple function that returns an HTML string using the html tag function. To make variables or functions available within the template (for interpolation or directives), you must use the useInsert hook.

javascript
                
import { html, RegisterComponent, useInsert } from "pawajs";

export const MyComponent = () => {
    const message = "Hello World";
    // Make 'message' available to the template
    useInsert({ message });
    return html`<div>@{message}</div>`;
};
RegisterComponent(MyComponent);
             
            

Props

Props are passed as the first argument. In PawaJS, props are functions (signals) that return the current value, ensuring reactivity. The only exception is children, which is the raw content passed to the component.

Since children is usually HTML content, it is typically inserted using standard JavaScript interpolation ${children} rather than PawaJS text interpolation @{}.

javascript
                
// Component definition
export const Greeting = ({ name, children }) => {
    useInsert({ name });
    return html`
        <h1>Hello, @{name()}</h1>
        <div>${children}</div>
    `;
};
RegisterComponent(Greeting);

// Usage
html`<greeting :name="'PawaJS'">Welcome to the docs</greeting>`
             
            

Rest Props (Attribute Fallthrough)

PawaJS provides a mechanism to automatically pass extra attributes to a specific element within your component. By adding the -- attribute to an element, it will receive all attributes that were passed to the component but not explicitly destructured in the props.

javascript
                
export const CustomButton = ({ label }) => {
    // 'label' is consumed here.
    // Any other attributes (like class, onclick, id) passed to <custom-button></custom-button>
    // will be applied to the <button></button> element below because of '--'.
    
    
    useInsert({ label });
    return html`
        <button class="base-btn-class" --="">
            @{label}
        </button>
    `;
};
RegisterComponent(CustomButton);

// Usage: class="bg-red-500" and on-click will fall through to the button
//html 
<custom-button :label="'Delete'" class="bg-red-500" on-click="handleDelete"></custom-button>
 
            

Slots (Template Props)

You can pass named slots to a component using the <template> tag with a prop attribute. These templates are extracted and passed as props to your component, while the remaining content becomes children.

javascript
                
// Component
export const Layout = ({ header, footer, children }) => {
    return html`
        <div class="layout">
            <header>${header()}</header>
            <main>${children}</main>
            <footer>${footer()}</footer>
        </div>
    `;
};
RegisterComponent(Layout);

// Usage
html`
    <layout>
        <template prop="header">
            <h1>My App Title</h1>
        </template>
        
        <p>This is the main content (children).</p>
        
        <template prop="footer">
            <p>© 2023</p>
        </template>
    </layout>
`
             
            

Async Components

Components are async by default. Simply add the async keyword to your component function to perform asynchronous operations like data fetching before rendering. PawaJS detects that the function returns a Promise and handles the rendering lifecycle accordingly.

To manage this, you must use the useAsync hook, which provides three essential tools:

  • onSuspense(html): Provides a fallback UI (e.g., a loading spinner) to display while the async operation is in progress.
  • $async(callback): A wrapper function that is required for any PawaJS hooks (like $state or useInsert) called after an await statement. It restores the component's context, which is lost during the async pause.
  • Error Boundaries (try/catch): By wrapping your async logic in a try...catch block, you can gracefully handle errors (like a failed API request) and return a fallback error UI instead of crashing the component.
javascript
                
import { html, useAsync, $state, useInsert, RegisterComponent } from "pawajs";

export const UserProfile = async ({ id }) => {
    // 1. Get async helpers
    const { onSuspense, $async } = useAsync();

    // 2. Define a loading state
    onSuspense(html`<div>Loading profile...</div>`);

    try {
        // 3. Perform async operation
        const response = await fetch(`/api/user/${id()}`);
        if (!response.ok) throw new Error('User not found');
        const userData = await response.json();

        // 4. Use $async to wrap hooks after 'await'
        const user = $async(() => $state(userData));
        $async(() => useInsert({ user }));

        // 5. Return the final template
        return html`
            <div class="profile">
                <h1>@{user.value.name}</h1>
                <p>@{user.value.bio}</p>
            </div>
        `;

    } catch (error) {
        // 6. Error Boundary: Return a fallback UI on error
        return html`
            <div class="error-message">
                <p>Could not load user: ${error.message}</p>
            </div>
        `;
    }
};

RegisterComponent(UserProfile);