AirNYT: Building the Map and Markers Interactivity

This tutorial is a part of a larger tutorial series on cloning the Airbnb map/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 various components that make up this map and cards interface.

This specific tutorial will cover how to turn this:

http://nytrecsalaairbnb.surge.sh

…into this:

http://airnyt.surge.sh

To do this, I’ll show you how I built out the following functionality:

  • A carousel slideshow on each card for each location
  • A interactive map with map markers that can be hidden/shown with a toggle switch
  • Hover events on cards that change styling on the map markers

There’s a lot to cover in this tutorial and I’ll try to skip as much boilerplate, yarn installations, and styling as possible when I’m showing how the different parts are coded. If you’d like to see the complete code, you can do so here:

https://github.com/kpennell/airnyt

A carousel slideshow on each card for each location

As you’ve likely seen, Airbnb property listings have these really elegant slideshows on each property:

These let you get the jist of the place without going to that page. To recreate that functionality in the AirNYT app, I’m going to use the React-slick library. This is a React version of the very popular Slick Carousel library.

The react-slick API was fairly straightforward to use. I created a separate CardCarousel component to house it. The component is a simple Slider component that rotates through different material-ui CardMedia images from my data.

To mimic the chevron right and left arrows from the Airbnb slider, I put these custom arrows in using material-ui icons:

 

Within the settings, I can tell the Slider component that I’d like to use these arrow components:

And voila, I have working sliders on all of my cards:

The one caveat I’d share about these sliders is that they definitely slow down the interface. We saw how fast React-virtualized made loading the 380+ cards in the last blog post. But now, each card is loading 3 images, and the lag is noticeable. To solve this, I’d recommend trying out some sort of virtualized or windowed slider that only loads the image when someone opens it or slides to it. But that being said, as long as we’re paginating these results (card listings), the lag shouldn’t be too terrible.

An interactive map with map markers

Maps are perhaps the original data visualization. For spatial or location data, a map is indispensable for visualizing the data. Airbnb has a really well-designed map that can be toggled with a simple switch and we can mimic this fairly easily.

But first, we need a mapping library! The gargantuan React ecosystem offers many options, both in terms of how they are loaded, how they are interacted with, and which tiles (e.g. Google, OSM) get loaded. I’ve tried several and have kept coming back to google-map-react. This library allows React developers to load any component as a marker on a Google map. I like how this library feels like a react-specific way of doing things (a la ‘everything is a component’) and it seems to get out of my way. If you wish to tweak things on the actual Google Maps JavaScript API, google-map-react gives you a simple API to do so.

My MapAndMarkers component is fairly straightforward. I’ll explain how it works after this code snippet:

GoogleMapReact is the primary google-map-react component here, and it needs some props and children to work. The required props include the API keys and center and zoom. Center and zoom tell the map which tiles to load. The options prop allows for controlling the aforementioned underlying Google JavaScript API. Things like panning, zooming, controls, etc. get controlled with this object. hoverDistance allows for controlling how far/close to a marker counts as mouse hovering.

To create the {MapMarkers} children, I .map over the locations array prop (my cards/locations data) and pass things like key, lat, and lng as props to the MapMarkers(s). These props tell GoogleMapReact where to render these components/points/markers. I’ll explain the hoveredCardId prop within the context of my MapMarker component.

My MapMarker component is fairly straightforward. The goal was to mimic the plain white tooltips that Airbnb uses:

I used CSS arrow please to create the CSS for this component and used this CSS to React tool to ‘JSSify’ my CSS. There are a variety of nifty React tooltip libraries out there, but I went this route to keep the map fast and have one less dependency.

When someone hovers on a Card in the list, I want the MapMarker background to turn purple, like Airbnb does:

To achieve this, I used the classNames library to toggle the class with the background color. So, if the id from the card (that is being hovered upon) is equal to id on the marker, the style changes. To set this id, I setup some functions to be called by mouse events:

// LocationCards.js

<Card className={classes.card} onMouseEnter={e => this.props.c(location)} onMouseLeave={e => this.props.resetCardMarkerHover()} >

setCardMarkerHover is a straightforward function that updates the state variable of hoveredCardId according to the card that is being hovered upon.

I know I’m skipping some of the steps here but please read through the code if you’d like to see how the state/props variables work here.

Toggle-able Map

Like I mentioned, I really like how Airbnb lets users toggle the map.

Sometimes you want a map and sometimes you just want a grid of cards (and more screen real estate). To recreate this functionality, I put a toggle switch in my FilterBar, like so:

This switch is on or off depending on the prop mapShowing. When it gets toggled, it calls the function toggleMapShowing. Fairly straightforward! Now, within my LocationsGrid component, I toggle the map like so:

The state variable mapShowing and the toggleMapShowing function live within App.js.

Getting the map to push the list over was achieved using flex-box (how did we go without it?!) and inline-block:

This allows the map to kindly push its way onto the scene, and the flexbox grid of cards responds accordingly. The last thing I’ll point out here is that the cards are flex-start aligned when the map is in and centered when the map is hidden. This makes for a more attractive ui and is achieved with this simple ternary on the parent <Grid>:

I didn’t do it here, but the next step would be to mimic the ✓ vs. X in the AirBnb toggle switch:

I’m fairly confident a bit of digging into the various props of the material-ui Switch component would allow for this.

This tutorial will be continued in the next section, where we will cover building:

  • 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 queries of the data
  • A responsive menu
  • General responsible responsiveness to make this app mobile-friendly
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 kyleapennall.com and many other digital spaces