AirNYT: Pagination + Filtering + Responsiveness

This tutorial picks up where we left off around building the map and markers. This is part of a larger tutorial series on cloning the Airbnb map and cards interface to be able to explore the last 7 years of New York Times travel recommendations. The short name for this app/series is AirNYT.
In this part of the tutorial, we will continue building out the following:

  • Working Pagination to make exploring the data easier/faster
  • A popup year filter that allows users to show NYT data by year
  • Text/Search Filtering to allow for string query filtering of the data
  • A responsive menu
  • General responsible responsiveness to make this app mobile-friendly

Our end goal is this:

Building working pagination to make exploring the data easier

If you play around with the Airbnb interface as much as I have, you’ll notice that they use infinite scroll when the map is hidden and pagination when the map is showing. This makes perfect sense, from the user’s perspective, as the map should only display a subset of the total properties. Well, you might be able to make some sort of cool infinite scroll updating map, but I don’t think most user would find this intuitive. I mention this background so as to say: we need pagination.
If Github stars are a proxy for popularity, then most React developers are using react-paginate for their pagination. Well, I tried and tried to like and use react-paginate but I found it lacking on the docs and props front. It felt sort of heavy and clunky and Angular 1-ey to me. After some Googling, I came across react-paginating. I found it’s API and examples much easier to understand so I went with this.
To achieve basic client-side pagination, we need two things:

  1. A pagination ui with next buttons and page numbers
  2. To appropriately slice our big array down to only display a subset of it on the appropriate page

This was achieved fairly easily with the following code:
I first needed to calculate a variety of values within my LocationsGrid component. I’ll comment what each of them do:

The following simple function changes the page:

As you can see, with very little code, we can calculate the appropriate props to give to our PaginationComponent:

The pagination component has a bit more going on, but it’s easy to figure out with some explaining. First, take a look a what we’re making:

Here’s the code:

I’ll break down what’s happening. Our pagination needs a previous and next arrows. Those are created using the material-ui icons (NavigateBefore and NavigateNext). We need a subset of pages to display, then an ellipsis …, followed by the last page. That’s what all of those are accomplishing.
The page < currentPage + 3 && page > currentPage – 3 && page != totalPages part is getting a very specific subset of the pages to display to user. In English, this says “show them a few behind the current page, a few ahead of the current page, but not the last page!”.
The section at the bottom of the code is telling the user how many of the listings they are seeing:

It’s a fair amount of code, but it ultimately adds up to a component that is pretty close the Airbnb version.



A client-side year filter that allows users to show NYT data by year

I really like the way Airbnb gives users a ton of different filters without cluttering up their ui. Their filter bar features buttons that queue popovers/modals to achieve this.

I mimicked this using Material-ui’s Popover component. Here’s how I created a filter that allows user to filter out different years from the total results.

ChipFilter is a component that allows users to click the years to toggle them on and off (and thus achieve this client-side filtering):

But how does the actual filtering itself happen? That takes place within our App.js:
State.years is an array of objects with key, label, and showing properties:

matchChipsAndTags is the filtering function that determines whether the location’s year matches up with the state.year’s object showing property. It does this with some ES6 kungfu:

This function takes the state.chips and the location.year as its parameters. Array.prototype().some is a newer array prototype function that stops executing once a truthy value has been found. So, in English, this function is returning true or false depending on whether that specific locations year property matches the currently showing chips.
So, for instance, a Card/Location like Istanbul will only get rendered (aka appear) if its year property (2013) is matches the chip.label of 2013 (it will) and is that particular chip’s showing property (within state) is true.

Ultimately, the array is filtered down like so:

Text/Search Filtering to allow for string queries of the data

Airbnb has a search box in their header that allows for easy searching of places/experiences:

We’re not going to recreate the whole server-side search experience/function here but I think some client-side filtering would work nicely. This is very easy to do in React.
First, within state in App.js, I’ll declare the search/string value:

I’ll then declare a function in App.js to change this value:

I’ll pass this function as a prop to my Header component. Within component, I’ll call the function within an InputBase component:

I use InputBase instead of TextField so as to avoid any of the default TextField styling (it underlines the search box). TextField is a component composed of several other underlying component, including InputBase.

This search filter box isn’t the most interesting component, but I did find it useful for doing country searches. It was fun to see when different places in the same country got that sweet NYT mention. Here’s what Mexico/Australia look like:

As you see, with New Mexico popping up in a Mexico, it would take some work and data formatting/reorganizing (e.g. a country property in the dataset) to make this truly a country only search. I would also love to have a continent search, but that’s for another tutorial.

A responsive menu

Airbnb’s menu crunches down into a full-screen mobile dropdown on mobile. This frees up a lot of space and only shows the user what they need to see. Here’s how we can achieve the same thing with material-ui.
Material-ui has a really helpful CSS in JSS API for handling breakpoints. You put the breakpoints right into your styles like so:

This translates to: large size screens and up will have this background style.

This would translate to: all the way down to the small size of screen will have this style.
You can set your breakpoints accordingly when you define your theme (usually in index.js or app.js) using createMuiTheme.
To achieve something like the airbnb mobile menu, I’ll first hide the normal menu items on small screens with this styling:

I’m going to create a dropdown menu, but first I’ll put the styling in for it:

[theme.breakpoints.up('md')]: { // for mid-size screens and up, please hide this !

I then create a DropDownMenu component like so:

This creates a little popover menu that is toggled using the down arrow next to the logo. It’s not the elegant full-screen menu that airbnb offers, but it’s definitely a good start.
To make this app more generally functional and appealing on mobile, I went through and made small tweaks in the styling using these same theme breakpoint helpers.

To do

This a fun little map + cards + filtering app. But there’s still work to be done. These are the items I’d like to cover in future tutorials:

  • Mobile Map Toggling

Right now, I’m hiding the map div on mobile. Lame! Airbnb has a nice way of toggling the mobile map vs. the cards/list using a little hovering button in the lower right corner of the screen. With a material-ui floating action button and some clever breakpoints and styling, we can make the map available on mobile.

  • Accounts and Favoriting

I’ll show how to incorporate firebase into this app to allow for user authentication and letting people save their favorite places.

  • Infinite Scroll on Cards View and Pagination on Map View

We made a really nice react-virtualized fast loading list in a previous tut. There’s no reason we can’t show this infinite loading list when the map isn’t visible and show the paginated list when the map is showing. We can get the best of both worlds this way.

  • Small ui tweaks

The list should scroll to the top when the page is changed. It could be helpful to center the map on the appropriate map marker when a user hovers on a card.

  • Pop Up Card on the Marker

Airbnb’s map markers show a little pop up card when you click on them. This gives you more info about the place and is quite useful. I’ll add these in a future tutorial.

Stay tuned for more tutorials and the coming end of this tutorial!

Kyle Pennell
Kyle Pennell is a marketer and writer with a variety of technical chops (JavaScript, Google Sheets/Excel, WordPress, SEO). He loves clear and helpful copy and likes automating things and making web apps (mostly maps). Found at and many other digital spaces