<ul class="posts"></ul>
<button type="button" class="btn">Fetch posts</button>
body {
  margin: 16px;
  line-height: 1.5;
  color: #212121;
  font-family: sans-serif;
}

p {
  margin: 0;
}

.posts {
  margin: 0;
  list-style: none;
  margin-bottom: 16px;
}

.posts li:not(:last-child) {
  margin-bottom: 16px;
}

.post-title {
  margin-top: 0;
  margin-bottom: 8px;
  font-size: 20px;
  font-weight: 700;
}

.post-title:first-letter {
  text-transform: uppercase;
}

.post-body {
  margin: 0;
}


const fetchPostsBtn = document.querySelector(".btn");
const postList = document.querySelector(".posts");

// Controls the group number
let page = 1;
// Controls the number of items in the group
let perPage = 10;

fetchPostsBtn.addEventListener("click", async () => {
  try {
    const posts = await fetchPosts();
    renderPosts(posts);
    // Increase the group number
    page += 1;

    // Replace button text after first request
    if (page > 1) {
      fetchPostsBtn.textContent = "Fetch more posts";
    }
  } catch (error) {
    console.log(error);
  }
});

async function fetchPosts() {
  const params = new URLSearchParams({
    _limit: perPage,
    _page: page
  });

  const response = await axios.get(
    `https://jsonplaceholder.typicode.com/posts?${params}`
  );
  return response.data;
}

function renderPosts(posts) {
  const markup = posts
    .map(({ id, title, body, userId }) => {
      return `<li>
          <h2 class="post-title">${title.slice(0, 30)}</h2>
          <p><b>Post id</b>: ${id}</p>
          <p><b>Author id</b>: ${userId}</p>
          <p class="post-body">${body}</p>
        </li>`;
    })
    .join("");
  postList.insertAdjacentHTML("beforeend", markup);
}

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.2/css/materialize.min.css
  2. https://cdnjs.cloudflare.com/ajax/libs/izitoast/1.4.0/css/iziToast.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/izitoast/1.4.0/js/iziToast.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/axios/1.5.1/axios.min.js