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

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay