DEV Community

artydev
artydev

Posted on

Fetching Data with UmaiJS

/** @jsx m */ import { m, mount, redraw } from "umai"; // Utility function to debounce inputs function debounce(fn, delay) { let timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; } function sleep(ms) { return new Promise((res) => { setTimeout(res, ms); }); } async function getRandomUsers() { // Simulate network delay await sleep(State.delay); // Fetch random users from the Random User API const response = await fetch("https://randomuser.me/api/?results=5"); if (!response.ok) { throw new Error("Network response was not ok"); } const data = await response.json(); return data.results.map((user, index) => ({ id: index + 1, name: `${user.name.first} ${user.name.last}`, email: user.email, picture: user.picture.medium, })); } let State = { users: [], delay: 200, loading: false, error: null, }; function saveUsers(users) { State.users = users; } function clearUsers() { State.users = []; redraw(); } async function fetchUsers(node) { State.loading = true; State.error = null; redraw(); clearUsers(); try { let users = await getRandomUsers(); saveUsers(users); } catch (error) { console.error("Failed to fetch users:", error); State.error = "Failed to fetch users. Please try again."; } finally { State.loading = false; redraw(); } } // User item component for clarity and reuse const UserItem = ({ user }) => ( <li class="user-item"> <img src={user.picture} alt={user.name} class="user-avatar" /> <div class="user-details"> <p><strong>{user.name}</strong></p> <p>{user.email}</p>  </div>  </li> ); const Users = () => { return ( <div> {State.loading && <Loader />} {State.error && <p class="error-message">{State.error}</p>}  {!State.loading && State.users.length === 0 && ( <p class="no-users-message">No users available. Click "Load Users" to fetch.</p>  )} <ul aria-live="polite" class="user-list"> {State.users.map((user) => ( <UserItem user={user} key={user.id} />  ))} </ul>  </div>  ); }; const Timer = () => { const handleInput = debounce((e) => { State.delay = parseInt(e.target.value, 10) || 0; redraw(); }, 300); return ( <div class="timer"> <label for="delay-input">Delay fetch for: </label>  <input type="number" id="delay-input" value={State.delay} oninput={handleInput} aria-label="Fetch delay in milliseconds" /> <span> ms</span>  </div>  ); }; const Loader = () => { return ( <div class="loader"> Loading... </div>  ); }; function setup(node) { let btnLoader = node.querySelector("button"); btnLoader.addEventListener("click", () => fetchUsers(node)); } const App = () => ( <div dom={setup}> <Timer /> <button class="load-button">Load Users</button>  <Users /> </div> ); export { App }; mount(document.body, App) 
Enter fullscreen mode Exit fullscreen mode
body { font-family: Arial, sans-serif; margin: 20px; } .timer { margin-bottom: 20px; } label { margin-right: 10px; } input { width: 80px; padding: 5px; font-size: 14px; } .load-button { padding: 10px 20px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; } .load-button:hover { background-color: #0056b3; } .loader { text-align: center; font-weight: bold; margin-bottom: 20px; } .error-message { color: red; text-align: center; margin-bottom: 10px; } .no-users-message { text-align: center; margin-bottom: 20px; } .user-list { list-style: none; padding: 0; } .user-item { display: flex; align-items: center; margin-bottom: 15px; } .user-avatar { border-radius: 50%; margin-right: 15px; } .user-details p { margin: 0; } 
Enter fullscreen mode Exit fullscreen mode

You can test it here :

Demo

Top comments (0)