3 min read

Tutorial: Show top 10 articles from Hacker News

💡
This is a tutorial that solves the "Show top 10 articles from Hacker News" exercise.
If you haven't already, try to solve it yourself first before reading the solution!

To get setup, let's clone the starter repo linked in the exercise at https://github.com/reactpractice-dev/hacker-news-top-10.

Next, let's go over Hacker News API docs to figure out how to retrieve the first ten top articles from the "Top Articles" API.

Scrolling over the docs, we can see the endpoint to retrieve the "Top Articles" is https://hacker-news.firebaseio.com/v0/topstories.json.

However, this only returns ids, so let's see how we can retrieve the details of each article; scrolling up a bit, the endpoint we're looking for is https://hacker-news.firebaseio.com/v0/item/<ID>.json

With the backend APIs in place, let's start building the UI.

Fetching the data

Let's first retrieve the data we need and log it to the console.
Since we need to do multiple calls, we can extract the fetching logic to its own method.

First, let's log just the top articles ids:

import { useEffect } from "react";

const fetchTop10Articles = async () => {
  const topArticlesResponse = await fetch(
    "https://hacker-news.firebaseio.com/v0/topstories.json"
  );
  const topArticleIds = await topArticlesResponse.json();
  console.log(topArticleIds);
};

const HackerNewsTop10 = () => {
  useEffect(() => {
    fetchTop10Articles();
  }, []);
  return <div>Build your component here!</div>;
};

export default HackerNewsTop10;

And indeed, it works. You'll notice the ids get rendered twice even if we only log them once - it's because of StrictMode, which is enabled by default in development (see main.jsx).

Now, let's pick 10 articles and fetch their details.

The trick in this challenge is how to combine JS iteration functions - e.g. map - with async functions. The solution? -  Promise.all. This way, we can iterate through the list of article ids, fetch the details of each, and when all promises complete, we have an array of article details.

const fetchArticleDetails = async (articleId) => {
  const articleResponse = await fetch(
    `https://hacker-news.firebaseio.com/v0/item/${articleId}.json`
  );
  return articleResponse.json();
};

const fetchTop10Articles = async () => {
  const topArticlesResponse = await fetch(
    "https://hacker-news.firebaseio.com/v0/topstories.json"
  );
  const topArticleIds = await topArticlesResponse.json();
  const top10Articles = topArticleIds.slice(0, 10);

  const articles = await Promise.all(
    top10Articles.map((articleId) => fetchArticleDetails(articleId))
  );
  console.log(articles);
};
...

Displaying the data

With the article details in place, let's display the data.
The challenge doesn't say how to show the data, but simply that score, title, url and author should be displayed. We can show it just like on Hacker News:

Here is the final completed code:

import { useEffect, useState } from "react";

const fetchArticleDetails = async (articleId) => {
  const articleResponse = await fetch(
    `https://hacker-news.firebaseio.com/v0/item/${articleId}.json`
  );
  return articleResponse.json();
};

const fetchTop10Articles = async () => {
  const topArticlesResponse = await fetch(
    "https://hacker-news.firebaseio.com/v0/topstories.json"
  );
  const topArticleIds = await topArticlesResponse.json();
  const top10Articles = topArticleIds.slice(0, 10);

  const articles = await Promise.all(
    top10Articles.map((articleId) => fetchArticleDetails(articleId))
  );
  return articles;
};

const HackerNewsTop10 = () => {
  const [articles, setArticles] = useState([]);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    setIsLoading(true);
    fetchTop10Articles().then((articles) => {
      setIsLoading(false);
      setArticles(articles);
    });
  }, []);
  return (
    <div>
      {isLoading ? (
        <p>Loading ...</p>
      ) : (
        <ul>
          {articles.map((article) => (
            <li key={article.id}>
              <div style={{ display: "flex", flexDirection: "column" }}>
                <a href={article.url}>{article.title}</a>
                <span>
                  {article.score} by {article.by}
                </span>
              </div>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default HackerNewsTop10;

You can check out the working repo over on Github: https://github.com/reactpractice-dev/hacker-news-top-10/blob/solution/src/HackerNewsTop10.jsx

How did you solve this challenge?
Share your solution or any thoughts and questions in the comments below!