# Example 4

### Description

This example demonstrates how to:
1. Fetch data from an external API (**[randomuser.me](https://randomuser.me/)**) using the platform's **`http`** utility (Nuxt 4 `useFetch` instance).
2. Render an interactive data **`<table>`** with **VanJS** displaying user details (photo, name, email, location) with clickable, highlightable rows.
3. Automatically refresh users whenever the tab is entered or refreshed using the **`onStepEnter`** hook.
4. Paint the **VanJS** form exclusively when refreshing/loading users by calling **`callback(formElement)`** within the refresh routine and never before.
5. Pass the selected user's full name to subsequent wizard tabs using **`setModelData(true, { userName: ... })`**.
6. Invalidate the step (`setModelData(false)`) until a user is explicitly selected.

### Code

```javascript
// 1. Import only the VanJS tags used below
const { div, span, table, thead, tbody, tr, th, td, img, p } = van.tags;

// 2. Reusable inline styles
const styles = {
    container: "padding: 1rem; font-family: system-ui, -apple-system, sans-serif; color: #1e293b; max-width: 680px;",
    header: "margin-bottom: 1rem;",
    title: "font-size: 1.1rem; font-weight: 600; margin: 0 0 0.25rem 0; color: #0f172a;",
    subtitle: "color: #64748b; font-size: 0.85rem; margin: 0;",
    table: "width: 100%; border-collapse: collapse; font-size: 0.85rem; margin-top: 0.75rem; border: 1px solid #e2e8f0; border-radius: 6px; overflow: hidden;",
    th: "background-color: #f1f5f9; color: #475569; font-weight: 600; text-align: left; padding: 0.6rem 0.75rem; border-bottom: 1px solid #cbd5e1;",
    row: "cursor: pointer; transition: background-color 0.15s ease; border-bottom: 1px solid #f1f5f9; background-color: white;",
    selectedRow: "cursor: pointer; transition: background-color 0.15s ease; border-bottom: 1px solid #93c5fd; background-color: #eff6ff;",
    td: "padding: 0.5rem 0.75rem; vertical-align: middle;",
    tableAvatar: "width: 32px; height: 32px; border-radius: 50%; object-fit: cover; display: block;",
    userCard: "margin-top: 1.25rem; padding: 1rem; border: 1px solid #bfdbfe; border-radius: 8px; background-color: #f0f9ff; display: flex; gap: 1rem; align-items: center;",
    cardAvatar: "width: 52px; height: 52px; border-radius: 50%; border: 2px solid #3b82f6; object-fit: cover;",
    userInfo: "flex: 1; min-width: 0;",
    userName: "font-weight: 600; font-size: 0.95rem; color: #0f172a; margin: 0 0 0.2rem 0;",
    userDetail: "font-size: 0.8rem; color: #64748b; margin: 0;",
    successBadge: "display: inline-block; margin-top: 0.35rem; padding: 0.2rem 0.5rem; background-color: #dcfce7; color: #166534; font-size: 0.75rem; font-weight: 600; border-radius: 4px;",
    errorText: "color: #dc2626; font-size: 0.85rem; margin-top: 0.5rem;"
};

// 3. VanJS reactive state
const usersList = van.state([]);
const selectedUserId = van.state("");
const selectedUser = van.state(null);
let isFetching = false;

// 4. Invalidate the step until a user is selected
setModelData(false);

// 5. User selection handler, triggered by clicking a row
function onUserSelected(uuid) {
    selectedUserId.val = uuid;
    const user = usersList.val.find((u) => {
        return u.login.uuid === uuid;
    });

    if (user) {
        selectedUser.val = user;
        const fullName = `${user.name.first} ${user.name.last}`;

        // Store the selected name and data for the other tabs
        setModelData(true, {
            userName: fullName,
            userEmail: user.email,
            userCity: user.location?.city,
            userAvatar: user.picture?.medium
        });
    } else {
        selectedUser.val = null;
        setModelData(false);
    }
}

// 6. Build the component with an interactive table
function buildForm() {
    const rows = usersList.val.map((u) => {
        const fullName = `${u.name.first} ${u.name.last}`;

        return tr({
            style: () => {
                if (selectedUserId.val === u.login.uuid) {
                    return styles.selectedRow;
                }
                return styles.row;
            },
            onclick: () => {
                onUserSelected(u.login.uuid);
            }
        },
            td({ style: styles.td },
                img({
                    src: u.picture?.thumbnail,
                    alt: fullName,
                    style: styles.tableAvatar
                })
            ),
            td({ style: styles.td }, fullName),
            td({ style: styles.td }, u.email),
            td({ style: styles.td }, `${u.location?.city}, ${u.location?.country}`)
        );
    });

    return div({ style: styles.container },
        // Header
        div({ style: styles.header },
            p({ style: styles.title }, "Responsible User Selection"),
            p({ style: styles.subtitle },
                "Click a table row to select the responsible user. Fields marked with ",
                span({ style: "color: #dc2626;" }, "*"),
                " are mandatory."
            )
        ),

        // Users table
        table({ style: styles.table },
            thead(
                tr(
                    th({ style: styles.th }, "Photo"),
                    th({ style: styles.th }, "Name"),
                    th({ style: styles.th }, "Email"),
                    th({ style: styles.th }, "Location")
                )
            ),
            tbody(...rows)
        ),

        // Confirmation card for the selected user
        () => {
            if (!selectedUser.val) {
                return null;
            }

            const u = selectedUser.val;
            const fullName = `${u.name.first} ${u.name.last}`;

            return div({ style: styles.userCard },
                img({
                    src: u.picture?.medium || u.picture?.thumbnail,
                    alt: fullName,
                    style: styles.cardAvatar
                }),
                div({ style: styles.userInfo },
                    p({ style: styles.userName }, fullName),
                    p({ style: styles.userDetail }, `✉ ${u.email}`),
                    p({ style: styles.userDetail }, `📍 ${u.location?.city}, ${u.location?.country}`),
                    span({ style: styles.successBadge }, "✓ Parameter 'userName' stored for the other tabs")
                )
            );
        }
    );
}

// 7. Fetch the users and paint the component through the callback
async function refreshUsers() {
    if (isFetching) {
        return;
    }

    isFetching = true;
    selectedUserId.val = "";
    selectedUser.val = null;
    setModelData(false);

    try {
        // Request through the 'http' utility (a Nuxt 4 useFetch instance)
        const { data, error } = await http("https://randomuser.me/api/?results=8");

        if (error && error.value) {
            throw new Error(error.value.message || "Could not fetch the users");
        }

        const payload = data && data.value ? data.value : data;

        if (payload && Array.isArray(payload.results)) {
            usersList.val = payload.results;
        } else {
            usersList.val = [];
        }

        // Call the callback only once the user refresh has completed
        callback(buildForm());
    } catch (err) {
        console.error("Error refreshing the users:", err);
        const errorView = div({ style: styles.container },
            p({ style: styles.errorText }, `The users could not be loaded: ${err.message || err}`)
        );
        callback(errorView);
    } finally {
        isFetching = false;
    }
}

// 8. Refresh the users automatically when the wizard tab is entered or refreshed
onStepEnter(() => {
    refreshUsers();
});
```

---
