Who are Most Popular Redditors? Here’s How to Find out While Learning ES6 and ES7

Jonny Kalambay
14 min readAug 13, 2018

--

Let’s rank the top posters in our favourite subreddit. All we need is a little Javascript.

I’m going to walk you though building a simple web app in which you can type in the name of your favourite subreddit, and a list of its top-scoring posters will appear below.

TLDR — Where’s the Code?

Here you go: https://github.com/jonnyk20/reddit-ranking

What we’ll Use

This is a perfect opportunity to learn and use some ES6 and ES7 to make our code nice and clean. Specifically, I’ll explain and use the following:

  • Arrow Functions
  • Template Literals
  • Fetch API
  • Async/Await
  • Object Property Shorthand
  • Object Destructuring
  • Default Arguments

If instead of (or in addition to) reading this, you would like a video walk-through with aquariums and Pokemon posters in the background, then here’s something just for you:

Before We Start

  • I’m hoping that you’re already comfortable with HTML and CSS, as well as the basics of Javascript, as well as how promises work.
  • Please make sure you use the latest version of Chrome to open this project (mine is 68.0.3440.84). With other browsers or older versions of Chrome, some of the syntax we’ll use may not be compatible and you’d have transpile the Javascript to make to make it work.
  • Pick you favourite subreddit to use as an example. I’m using ‘/r/learnjavascript’ but you’re welcome to use another as you write yours.
  • For each of the ES6 and ES7 concepts we’ll be using, I’ll give a brief overview. However, I’ll also hyperlink each topic to link to further reading.

The Set Up

Our app will simply show an input and a button and will work as follows:

  1. You type the name of a subreddit in the input.
  2. You press enter or click the button.
  3. After a few seconds, a ranked list of cards with the subreddit’s top-scoring posters will appear.
  4. Each user card contains the username, the number of posts from that user, and the total score from those posts.
  5. Clicking on the card opens that user’s profile on Reddit

The use is pretty simple, and so is the structure. All we need is 3 files. Create a new folder for this project, and within it, create index.html , style.css, and script.js.

In index.html, create a simple html skeleton. In the head section, import your style.css and script.js files. Add a defer attribute to the script tag so that it doesn’t get run until your document is loaded.

In the body, add a h1, and a form with an input and a button. Below the form create div that will serve as a container for the results. Add descriptive id attributes to the form, the input, and the results container.

<!DOCTYPE html>
<html>
<head>
<title>Top Redditors</title>
<link rel="stylesheet" type="text/css" media="screen" href="style.css" />
<script src="script.js" defer></script>
</head>
<body>
<h1>Top Redditors</h1>
<form id="subreddit-select-form">
<label>r/</label>
<input id="subreddit" />
<button type="submit">Rank</button>
</form>
<div id="results-container">
</div>
</body>
</html>

In style.css, add the following styles:

body {
text-align: center;
color: #0079d3;
}
button {
background-color: #0079d3;
color: white;
border-radius: 2px;
border: none;
}
#results-container {
display: flex;
flex-direction: column;
}
.user {
text-decoration: none;
padding: 5px;
border: solid 1px #0079d3;
margin: 5px auto;
}

You won’t be making any more changes to either of those files. script.js is where where most of the work will happen. In it, to begin with, you can just put in a console log to make sure that everything is working fine.

console.log('hello from script.js')

Open index.html from Chrome and you should see the following page.

Use Command+Option+J (mac) or Control+Shift+J (PC) to open the console and see the message from script.js.

Reddit API 101

We can’t write Javascript to interact with Reddits API if we don’t understand it first. Let’s take a look at how to receive JSON from Reddit and how to interpret it.

Getting JSON Data

To get JSON about a resource from Reddit’s API, all we need to do is add ‘.json’ to the end of a url that we’d normally use to access an htm view of that resource. For example, so see all the posts from /r/learnjavacript, we’d normally type in ‘https://www.reddit.com/r/learnjavascript’ to make a get request to that enpoint. To receive the post data in JSON, we just need to make a get request to ‘https://www.reddit.com/r/learnjavascript.json’.

Try it right now by tying that into the browser’s address bar and pressing enter. You’ll end up with something like this:

It’s impossible to read as is, so to see the structure of the response, open the ‘Network’ tab of your devtools and from the left tab, click on the request called ‘learnjavascript.json’ to open it (refresh the page if you don’t see it). Then, click the ‘Preview’ tab on the right side to see the object that comes back.

How to use Params for Limits and Pagination.

You can add parameters to your request by adding a question mark ‘?’ at the end of your request line followed by the parameters. The param key and values are separated by equal signs and each parameter is separated by an ampersand ‘&’.

In our final product, the requests will look like the following:

https://www.reddit.com/r/learnjavascript.json?limit=100&after=d8duc7vds7

But that does ‘limit’ and ‘after’ mean?

Limit is the easiest to unresdand. By default, each requests will retrieve 25 posts, but but you can increase that to any number up to 100 by adding limit= followed by the number of posts desired.

However, most popular subreddits have far more than 100 posts, so we’ll need to make multiple requests to get more. Reddit helps us out by providing an ‘after’ property in its response. This string of characters is kind of like a bookmark that we can use to tell reddit where we left off if we want to go and fetch more posts.

If, let’s say, subreddit has 1000 posts. Our first request will return posts 1–100, along with an ‘after’ property of, for example ‘123acb’. With that, we can make another request to reddit like this…

https://www.reddit.com/r/learnjavascript.json?limit=100&after=123abc

...and the Reddit api will automatically know to give us posts 101–200 in its response. In that second response, it will include another ‘after’ property, perhaps something like ‘xyz987’. We can use that to fetch posts 201–300...and so on and so on. This process may continue until there are no more posts to fetch, in which case the ‘after’ property in the response will be null.

If you’d like to, feel free to use a tool like Postman to play around with this before we start writing code.

The Main Event: Javascript

Everything is set up, so it’s time to write our script.

I’m going to lay out the functions, and then write them out in their entirety, while explaining in detail what everything does. In addition to that I encourage you to stop periodically as you follow, (every 5 minutes, for example), refresh index.html and make sure that the output is what is should be at that point. You can do so in one of two ways:

  • Put console.log statements in the functions and print out variables
  • Open the ‘sources’ tab in the devtools, find scrtipt.js and put breakpoints in the script before refreshing the page in order to use the debugger and check your variable values

Script Structure

Our script will consist of 4 main functions:

  • handleSubmit: captures subreddit name from the input and then calls fetchPosts
  • fetchPosts: retrieves post from then Reddit API and then calls parseResults
  • parseResults: converts API response objects into ranked list of users and stats and then calls displayRankings
  • displayRankings: turns list of users into HTML displays it

Also we need declare some descriptive variables at the top:

  • postsPerRequest: we’ll set this as the maximum allowed, 100
  • maxPostsToFetch: Our requests will happen sequentially, so we need to limit them so that our script doesn’t take too long, let’s choose 500
  • maxRequests: maxPoststoFetch divided by postPerQuest, so in this case, 5. After we’ve reached this number, we want to stop our script from making any more requests to the Reddit API
  • responses: An array that we’ll use to store the responses from all of the requests that are made

And at the bottom of the script, we need to select our subreddit selection form and attach an event listener so that whenever it’s submitted, handleSubmit is triggered.

At this point, our script might look something like this:

const postsPerRequest = 100;
const maxPostsToFetch = 500;
const maxRequests = maxPostsToFetch / postsPerRequest;
const responses = [];
const handleSubmit = e => {
// Capture subreddit name from input
};
const fetchPosts = (subreddit, afterParam) => {
// Fetch from Reddit
parseResults();
};
const parseResults = responses => {
// Rank Users by Post Score
displayRankings();
};
const displayRankings = results => {
// Attach Rankings to DOM
};
const subRedditSelectForm = document.getElementById('subreddit-select-form');
subRedditSelectForm.addEventListener('submit', handleSubmit);

If you’re not familiar with ES6, then a couple of things here might confuse you:

  • The keyword ‘const’: The ES6 way of writing code recommends using const and let instead of var to declare variables. In short, use const for variables that will not be reassigned and use let for those that may
  • Arrow functions: Arrow functions (const myFunction = arg => {/* do things */})work mostly the same way as functions written in the traditional way (var myfunction = function(){/* do things */} ) but look cleaner. The functional differences of arrow functions are irrelevant to this project, but definitely useful, so I recommend that you read about how they work.

All we need to do now is will out our four main functions.

  1. handleSubmit

Let’s start with the first and the easiest, handleSubmit. For this one, we just need to cancel the default submission event (because we don’t want to actually submit a form anywhere), target the input, find its value (the text that was typed in), save that value to a variable and then call fetchPosts, passing in that variable as an argument. You should end up with something like this:

const handleSubmit = e => {
e.preventDefault();
const subreddit = document.getElementById('subreddit').value;
fetchPosts(subreddit);
};

2. fetchPosts

This function will make requests to Reddit’s API and then save the responses. This is a asynchronous action, as the request will return a Promise, so we need to handle that accordingly. We’re going to use ES7’s async/await which is syntactical sugar built on promises. We define a function with the async keyword, and then within that function, you can write await followed by a promise and the code that follows that line will not get executed until that promise is resolved.

Basically, instead of:

var myfunction = function() { 
fetchDataFromAPI.then(function(data){
console.log(data);
}
)
};

We would have

const myFunction = async () => {
const data = await fetchDataFromAPI();
console.log(data)
}

Now that we understand async/await, there’s one more thing I’d like to introduce before filling in this function, and that is template literals. With ES6, instead of putting variables into strings like this:

var age = 25;
var greeting = 'I am ' + age + ' years old';

You can write a template literal using backticks and then insert Javacript into it using a dollar sign and two curly braces.

const age = 25;
const greeting = `I am ${age} and will be ${25 + 1} next year`;

This syntax will be useful for writing our request function, as the url will change based on variables. Specifically, it’ll depend on the ‘subreddit’ and ‘afterParam’ variables, so make sure you write both of those as parameters for the function (between the parentheses). Then, put a space and the async keyword after the equal sign in your fetchPosts. Your function should like this:

const fetchPosts = async (subreddit, afterParam) => {
// Fetch from Reddit
parseResults();
};

Now let’s fill it in. The function needs to do the following:

  1. Make a requests to Reddit’s API based on the desired subreddit, the limit we’re setting on the number of posts to retrieve, and the ‘after’ parameter.(Note: ‘after’ will be undefined the first time the function runs, that is, when we make our first request to the Reddit API)
  2. Wait for the response from that request and then save it to a variable
  3. Read that response and save it as JSON. This process is also asynchronous, so we need to await the function call and then save the result into a variable
  4. Push the JSON response into the responses array near the top of your script

The next step after that will depend. It can go one of two ways…

5-A) If the response of the request you just made contains an after property that isn’t null AND you haven’t reached our predefined number of maximum requests (found by checking the length of your response array), then recursively call fetchPosts again make another requests to fetch the posts that follow. You can do this by passing in the ‘after’ property from the request you just fetched and then attaching that as a param on the following request.

5-B. If the after property of the response is null OR you’ve reached your max number of requests. Stop making requests and parse all the posts that you’ve requested by calling parseResults on the responses array.

Here’s what this looks like in code:

const fetchPosts = async (subreddit, afterParam) => {
const response = await fetch(
`https://www.reddit.com/r/${subreddit}.json?limit=${postsPerRequest}${
afterParam ? '&afterParam=' + afterParam : ''
}`
);
responseJSON = await response.json();
responses.push(responseJSON);
if (responseJSON.data.afterParam && responses.length < maxRequests) {
fetchPosts(subreddit, responseJSON.data.afterParam);
return;
}
parseResults(responses);
};

2. parseResults

This function is in charge of turning our list of responses into a ranked list of users. Before walking through the pseudocode, I need to introduce three more ES6 concepts:

Implicit return: By omitting the curly braces, arrow function can return value simplicity, that is, without using the return keyword.

var myFunction = function(){ return 5 + 1 };

..has the same output as:

const myFunction = () => 5 + 1

Spread Operator: When you write the spread operator ... before an object, it takes all of properties of that object (or items of that array) and spreads them out so that they are interpreted as individual items. This is useful if you want to insert all the items of an array into another array as opposed to inserting the array itself (the latter case would end up in a nested array, which we don’t want).

That means that, if we want to combine two arrays, this wouldn't work:

const arr = [1, 2, 3];
arr.push([4, 5, 6]);
arr // => [1, 2, 3, [4, 5, 6]]

But this would:

const arr = [1, 2, 3];
arr.push(...[4, 5, 6]);
arr // => [1, 2, 3, 4, 5, 6]

Parameter Destructuring: In a function, we can access the property of an parameter directly by replacing the parameter’s name with a set of curly braces surrounding the property(ies) we want to access. This makes it easier to use the value in our function.

Let’s say I am passing into a function an employee record stored as an object:

const employee = {
name: 'Jonny',
shifts: ['Tuesday', 'Thursday'],
contact: {
phone: '555 5555',
email: 'jonny@email.com'
}
}

Instead of having to do this:

const nextShift = (person) => {
return `${person.name}` is coming in on ${person.shifts[0]}`;
}

I can write my function as follows:

const nextShift = ({ name, shifts }) => {
return `${name}` is coming in on ${shifts[0]}`;
}

We can even get properties that are further nested in the argument, by using the following syntax:

const getPhoneNumber = ({ name, contactInfo: { phone } }) => {
return `${name}`'s phone number is ${phone}`;
}

Object Property Shorthand: If you’re defining an object’s property and assigning a value to it by using a variable with the same name, you can clean it up by simply writing the name of the property. Here’s a simple example:

Instead of writing:

const age = 25;
const person = {
age: age
}

We cam simply write:

const age = 25;
const person = {
age
}

Now that we understand our toolbox, let’s lay out the blueprint. Our parseResults needs to do the following:

  1. Make an array called allPosts in which to store all the posts
  2. Loop through the response objects from the responses array, and at each one, find the array of posts (response.data.children) and push each post into allPosts.
  3. Create an empty object to store the user stats and call it statsByUser. In this object, each key will be a username, and that key’s value will be an object with the user’s score and post count.
  4. Loop through each post from allPosts and add the info to statsByUser, at each iteration, one of two things can happen:

5-A) If this post’s user doesn’t yet exist in statsByUser, create it, make the post count 1 and make the score the score from the current iteration (post)

5-B) If this post’s user exists in statsByUser increase the post count by 1 and add the score of the current post to the already existing score

6. In order to facilitate sorting, convert the statsByUser object to an array by getting the keys of the object using Object.keys as an array and then mapping that array into an array of objects, each with the user’s username, score, and post count

7. Sort that array in descending order by score

8. Call displayRankings with the sorted list

This is what it will look like in in code:

const parseResults = responses => {
const allPosts = [];
responses.forEach(response => {
allPosts.push(...response.data.children);
});
const statsByUser = {};allPosts.forEach(({ data: { author, score } }) => {
statsByUser[author] = !statsByUser[author]
? { postCount: 1, score } // score
: {
postCount: statsByUser[author].postCount + 1,
score: statsByUser[author].score + score
};
});
const userList = Object.keys(statsByUser).map(username => ({
username,
score: statsByUser[username].score,
postCount: statsByUser[username].postCount
}));
const sortedList = userList.sort((userA, userB) => userB.score - userA.score);displayRankings(sortedList);
};

displayResults

This last function simply turns our sorted list into HMTL elements and inserts them into our page.

We don’t need to learn any additional concepts for this one. However, it will help that the forEach function available to arrays has passes in each item’s index as the second argument. We can make use of this if we want to display the ranking by number.

This function should simply loop though each item in the array that is passed into it, and on each iteration, it should do the following:

  1. Use the index to determine that user’s rank (index + 1, since arrays begin with index 0)
  2. Create an anchor element and save into a variable called userCard.
  3. Add an href property to useCard that links to the user’s profile on Reddit
  4. Add text to useCard that contains the user’s username, number of posts, and total score
  5. Select the results-container in the DOM by its ID
  6. Append userCard to the results container

Here’s what the function should look like:

const displayRankings = results => {
const container = document.getElementById('results-container'); results.forEach(({ username, score, postCount }, i) => {
const rank = i + 1;
// Text
const userCard = document.createElement('a');
userCard.href = `https://www.reddit.com/user/${username}`;
userCard.classList.add('user');
userCard.innerText = `${rank}. ${username} - ${postCount} post(s) - (${score}) `;
container.appendChild(userCard);
});
};

And We’re Done!

Try it out by opening index.html, typing in the name of a subreddit into the input and then pressing enter or clicking the button. Give it a few seconds for your requests to be made, and then the ranked list should appear below the input. If you want to further challenge, add some form validation and request error handling. Tip: try/catch can be useful for error handling with async await and fetch.

I hope you enjoyed learning about some fancy new Javascript techniques, as well as the Reddit API. Please reach out if you have any questions. Check out my other articles or my Youtube channel for more tutorials.

--

--