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)