Hooks

PawaJS provides a set of powerful hooks to manage component state, lifecycle, and side effects.

$state

The core of PawaJS reactivity. $state creates a reactive object that automatically triggers UI updates when its .value property changes. It can be used inside or outside components.

js

                 
import { $state } from 'pawajs';

// Global state, accessible anywhere
const appName = $state('My Pawa App');

console.log(appName.value); // "My Pawa App"
appName.value = 'New App Name'; // Will trigger updates in any component using appName 

        
js

                 
import { $state, useInsert, html } from 'pawajs';

const MyComponent = () => {
    const count = $state(0); // Reactive number
    const user = $state({ name: 'Alice', age: 30 }); // Reactive object
    const items = $state(['apple', 'banana']); // Reactive array

    useInsert({ count, user, items });

    return html`
        <p>Count: @{count.value}</p>
        <p>User: @{user.value.name}, @{user.value.age}</p>
        <ul>
            <li for-each="item in items.value">@{item}</li>
        </ul>
    `;
}; 

        

$state can also be initialized with a function that returns a value or a Promise for asynchronous state.

useInsert

Makes variables, state, and functions available within a component's template. It's how you "inject" your component's logic into its HTML.

js

                 
import { $state, useInsert, html } from 'pawajs';

const MyComponent = () => {
    const name = $state('World');
    const greet = () => `Hello, ${name.value}!`;

    useInsert({ name, greet }); // Expose 'name' state and 'greet' function

    return html`
        <h1>@{greet()}</h1>
        <input type="text" @value="@{name.value}" on-input="name.value = e.target.value">
    `;
}; 

        

runEffect

Executes side effects. It can run on component mount, before mount, on reactive dependency changes, or as a read-only reactive effect. It can also return a cleanup function.

js

                 
import { runEffect, $state } from 'pawajs';

const globalCounter = $state(0);

// Global effect: logs when globalCounter changes
runEffect(() => {
    console.log('Global counter changed:', globalCounter.value);
}, [globalCounter]); 

        
js

                 
import { $state, runEffect, useInsert, html } from 'pawajs';

const MyEffectComponent = () => {
    const count = $state(0);

    // Runs once on mount, returns cleanup
    runEffect(() => {
        console.log('Component mounted!');
        return () => console.log('Component unmounted!');
    }, null);

    // Runs before mount (number of milliseconds)
    runEffect(() => {
        console.log('Running before mount (100ms delay)');
    }, 100);

    // Runs when 'count' changes
    runEffect(() => {
        console.log('Count changed:', count.value);
    }, [count]);

    useInsert({ count });

    return html`
        <button on-click="count.value++">Increment: @{count.value}</button>
    `;
}; 

        

useContext & setContext

The Context API allows you to pass data deep down the component tree without manually passing props at every level. setContext creates a context handle, and useContext consumes the value from a parent provider.

js

                 
import { setContext, useContext, useInsert, html, $state } from 'pawajs';

// 1. Create a context handle
const ThemeContext = setContext();

// 2. Provider component
const ThemeProvider = ({ children }) => {
    const theme = $state('light');
    ThemeContext.setValue({ theme }); // Set the context value
    useInsert({ children });
    return html`<div>${children}</div>`;
};

// 3. Consumer component
const ThemeButton = () => {
    const { theme } = useContext(ThemeContext); // Consume the context
    useInsert({ theme });
    return html`
        <button on-click="theme.value = theme.value === 'light' ? 'dark' : 'light'">
            Toggle Theme: @{theme.value}
        </button>
    `;
};

// Usage:
// <theme-provider><theme-button></theme-button></theme-provider> 

        

useRef

Creates a mutable ref object that can hold a reference to a DOM element or component instance. Use it with the ref directive.

js

                 
import { useRef, useInsert, html, runEffect } from 'pawajs';

const FocusInput = () => {
    const inputRef = useRef(); // Create a ref object

    runEffect(() => {
        // Access the DOM element after it's mounted
        if (inputRef.value) {
            inputRef.value.focus();
        }
    }, null); // Run on mount

    useInsert({ inputRef });

    return html`
        <input ref="inputRef" type="text" placeholder="I will be focused!">
    `;
}; 

        

useValidateComponent

Provides runtime prop validation for your components, helping ensure type safety and required props.

js

                 
import { useValidateComponent, html } from 'pawajs';

const MyValidatedComponent = ({ title, count }) => {
    // ... component logic
    return html`<div>@{title()} - @{count()}</div>`;
};

useValidateComponent(MyValidatedComponent, {
    title: { type: String, strict: true, err: 'Title is required!' },
    count: { type: Number, default: 0 }
}); 

        

useServer

A hook for isomorphic components, providing utilities to serialize data on the server and retrieve it during client-side hydration (Continuity Rendering Model).

js

                 
import { useServer, useInsert, html, isResume } from 'pawajs';

const ServerDataComponent = () => {
    const { setServerData, getServerData } = useServer();
    let initialData = { value: 'Loading...' };

    if (isResume()) { // Only available during client-side hydration
        initialData = getServerData();
    } else {
        // This part runs on the server during SSR
        // setServerData({ value: 'Data from Server!' });
    }

    const data = $state(initialData.value);
    useInsert({ data });

    return html`<p>Data: @{data.value}</p>`;
}; 

        

useAsync

Enables asynchronous component rendering. Use $async to wrap PawaJS hooks that depend on awaited values, ensuring they run in the correct context.

js

                 
import { useAsync, $state, useInsert, html } from 'pawajs';

const AsyncUserComponent = async () => {
    const { $async, onSuspense } = useAsync();

    onSuspense(html`<div>Loading user data...</div>`); // Optional loading state

    const response = await fetch('/api/user/1');
    const userData = await response.json();

    const user = $async(() => $state(userData)); // Reactive state from async data
    $async(() => useInsert({ user }));

    return html`
        <div>
            <h2>@{user.value.name}</h2>
            <p>Email: @{user.value.email}</p>
        </div>
    `;
}; 

        

isResume

A boolean hook that returns true if the current component is being hydrated on the client after server-side rendering, and false otherwise. Useful for client-only logic during hydration.

js

                 
import { isResume, useInsert, html } from 'pawajs';

const HydrationCheck = () => {
    const isHydrating = isResume();
    useInsert({ isHydrating });

    return html`
        <p>Client-side hydration: @{isHydrating ? 'Yes' : 'No'}</p>
    `;
}; 

        

forwardProps

Used within a component to explicitly forward any unconsumed props (including rest props --) to a nested element or another component. This is crucial for building wrapper components that need to pass down attributes.

js

                 
import { forwardProps, useInsert, html } from 'pawajs';

const MyWrapperButton = (props) => {
    forwardProps(props); // Forward all incoming props
    useInsert({ props });

    return html`
        <div class="wrapper">
            <button -->Click Me</button> <!-- Rest props will apply here -->
        </div>
    `;
};

// Usage: <my-wrapper-button class="my-class" data-id="123"></my-wrapper-button> 

        

useInnerContext

Allows a component to access the context of its immediate parent element or component. This is useful for tightly coupled components where a child needs to interact with its direct parent's state or methods.

js

                 
import { useInnerContext, useInsert, html, $state } from 'pawajs';

const ParentComponent = () => {
    const parentState = $state('I am the parent');
    useInsert({ parentState });

    return html`
        <div state-parent-data="parentState.value"> <!-- Inline state creates context -->
            <child-component></child-component>
        </div>
    `;
};

const ChildComponent = () => {
    const parentContext = useInnerContext(); // Access parent's context
    useInsert({ parentContext });

    return html`
        <p>Message from parent: @{parentContext.parentData.value}</p>
    `;
};