Content has not been updated for more than 2 years, proceed with caution.
Advanced mapping with Gatsby and React Leaflet
Hang on to your hats - this is a blustery one! In this post we walk through creating an imaginary real estate mapping app using Gatsby and React Leaflet.
We are using v3.0 of react-leaflet which was released at the end of 2020 - this code won’t work for v2 but I have working code for v2 of react-leaflet so feel free to ask me questions.
You can browse the finished code on GitHub and checkout a live demo of what we will be building. If you want to see a production version of this code you can poke around www.mindmapbc.ca/map.
A couple of notes before we get going:
-
I assume you have a basic familiarity with Gatsby and React hooks - but do my best to walk you through things. There just isn’t space here to explain what hooks like
useState
oruseEffect
do. -
I focus almost entirely on the javascript side of things in this post so what we build ain’t pretty!
-
Most of the code in this post is just plain React and would be transferable with to other React based frameworks, e.g. NextJs
-
If you are already familiar with react-leaflet you can likely skip straight to part 2 where we begin covering the more advanced mapping topics.
Part 1: Initial map setup
We are going to setup a quick starter project, add our dependencies, display a basic map and then add some imaginary placeholder data. In a real world application your geolocation data would be coming from an API/backend of some kind.
Step 1: Installation and dependencies
Here we are using Gatsby Theme Catalyst (but you can use any starter you want) to quickly bootstrap a working Gatsby project.
Next you need to add gatsby-plugin-react-leaflet
to your Gatsby plugins array.
Step 2: Adding our map
Now let’s add a very basic map to our project. Create a new page at src/pages/map.js
and copy paste the following code.
Save and refresh and you should be looking at a simple map on your page!
There are two gotchas worth noting. The first is that because Gatsby is statically rendered at build time you need to check that the component has mounted before rendering the map to the DOM. This is what our useHasMounted hook does for us. The second gotcha is that the map needs to have a height, without the height you will be staring at a blank screen.
Step 3: Create some placeholder data
Now let’s give ourselves a little bit of placeholder data to work with. The GeoJson format is nice to work with and is a becoming standardized across different mapping contexts making it easier to move data around if you need to. It generally follows the shape below where the geometry object stores information about geolocation and the properties object stores metadata about the location.
Copy and paste this placeholder data into your Gatsby app at src/data/geojson.json
.
Step 4: Adding GeoJson data to the map
To add our imaginary real estate data to the map we will use the GeoJson component from react-leaflet which we will import and use to replace the Marker and Popup components from our initial map.
Here is what your map.js
file should look like now.
Save and refresh your browser, you should see our new markers on the map. The GeoJson component automatically creates the markers for us - now we are getting somewhere!
There are two gotchas worth noting here with GeoJson in react-leaflet. The standard coordinate format in GeoJson and react-leaflet is different; for GeoJson coordinates are stored as [lng, lat]
whereas react-leaflet uses coordinates in the [lat, lng]
format. This has tripped me up more than a few times! The second gotcha we cover below and has to do with dynamic data.
Now that we have a basic map we can start building the more advanced functionality our real estate app needs.
Map tiles
Just a quick word about map tiles. In the demo code you will notice that I am using OpenStreetMap tiles. This is fine for testing or development but in production you will need to use, and pay, for a map tile service. I reviewed a few different options and ended up going with MapBox for my projects but there are lots of different options to choose from.
Part 2: Advanced mapping patterns
The core purpose of mapping applications is to visualize geographical data that would otherwise be too difficult to understand. Mapping applications have become ubiquitous in our lives whether it is for driving directions, restaurant reviews, or booking a hotel room. In order to help users find what they are looking for most mapping applications implement a robust searching and filtering UI. This is where the code complexity starts to ramp up.
In this section I am going to talk about clustering map markers, implementing custom marker popups, dynamic map data (e.g. data changes after filtering), dynamically showing only the results that are visible in the viewport, and finally adding a “locate me” button.
This code is for react-leaflet v3.0 which was a fairly major change from v2.x.x and involves a switch to a hooks based API.
Where the logic happens
I originally wrote the majority of this code for v2.x.x of react-leaflet and had to update it to work with v3.0. The biggest change in my experience was how you access the leaflet map element, and its child objects like layers and panes. In 2.x.x you can create a ref with useRef
to the map component and then use this ref to access the leaflet map element. In v3.0 accessing the map element is simplified in a react hook, however this also means that you need to be inside the MapContainer
component to use the hook.
We are going to add our logic to separate functional components, e.g. GetVisibleMarkers
and to the map page itself. Here is an outline of what that final page structure will look like once we complete all the steps below. You might find it helpful to reference the code on GitHub and this post simultaneously to see the code snippets in context of the larger project.
Clustering your map markers
As our real estate app gains more data points a common practice is to visually group or cluster nearby map markers together for a better user experience.
The first thing we will do is install react-leaflet-markercluster. We are working with v3.x of react-leaflet so you need to make sure that you install at least v3.x of react-leaflet-markercluster.
We are also going to add the FeatureGroup component at this same time, as we will need it later on. Both the cluster component and the feature group component will need refs as we will need to track data from these components later on. At this stage we are also going to import Leaflet itself since we will be doing some customizing of the markers Leaflet creates. Finally we are importing a little bit of CSS as well, more on that below.
Next we want to control the appearance and colors for these clusters - the default colors do not have enough contrast in my opinion. To do this we need a custom icon creation function. Here is what that can look like giving you the ability to style the clusters in different colors based on how many items are inside of each cluster.
Now create a custom css file for your map at src/lib/map.css
and copy and paste the css code from GitHub to your new file. You will notice there is some custom code in there for popups as well - don’t fear that is coming up next!
Now you should see your markers cluster together beautifully and those clusters dynamically update in size as you zoom the map in and out.
Adding custom popups to your markers
Next lets add some custom popups to our GeoJson markers. These popups or tooltips are used to display some basic information about a map marker - in the case of our real estate application you might want to show the home price, number of bedrooms, etc.
The GeoJson component has a prop called onEachFeature
which we can use to specify a function for creating a popup on each feature or marker. In this example the popup is very simple but you can use CSS to style the popup and make it look as fancy as you want. While I don’t cover it here you can extend this concept to also customize the visual appearance of the marker itself.
Next we need to assign the createPopups
function to the onEachFeature
prop for the GeoJson component.
Now you should see some plain popups appearing for each marker on the map. You can style and customize these popups with css and html, and at this point the sky is the limit really. If you were following along you should already have these styles in your src/lib/map.css
file and that file should already be imported as well.
Voila you have some custom popups for your map markers!
Dynamic map data
Almost all mapping applications involve dynamic data - data that changes in real time due to user input (e.g. searching or filtering results). In our imaginary real estate app users might want to filter the results to show homes within a specific price range, homes only with a certain number of bedrooms, etc.
In the demo I created some very rudimentary buttons to simulate this, but in a production application you could handle this in a number of different ways depending on your needs. I have used FuseJS successfully for this in previous projects.
Now, traditionally in React you would just update state and then the appropriate components would rerender with the new data. However leafletjs and by extension react-leaflet prevent rerenders by default meaning we need to do a little bit of trickery behind the scenes to force a rerender with our filtered data.
Here is how this can be achieved.
A bit of an explanation of what is happening here. We initialize two states; one to hold our GeoJson data, and one to hold a random string value for the key prop in React. In React when the key prop changes it forces a rerender. Lastly we have a useEffect
hook that dynamically sets a new key value any time the displayed markers are updated.
In the demo you can test this out with the buttons I built to simulate filtering the data on the map. We have a dynamically updating map as the geojson data changes! Try commenting out the useEffect function in your code to see what happens without it.
Dynamically updating the map viewport
As your data changes you also want the map to dynamically adjust the viewport (zoom and center) to include the newly filtered data, this is especially important in larger mapping applications. This saves someone the trouble of having to pan or scroll the map to see their new search results.
Because of react-leaflet’s refactor to hooks we can handle this logic in its own discrete component.
Next we need to add this component below the other components inside the MapContainer. Here is what the component tree should now look like inside of map.js
.
Tracking the visible markers
Most mapping applications display a list of search results beside or below the map. It is common to also have these results dynamically update as the user pans or zooms the map. Effectively the map itself becomes its own filtering tool for the user as they search. To accomplish this we need to add some more state to our imaginary real estate application and then update that state as the map pans or zooms. Again hooks allow us to extract this logic into its own component.
Now we need to add this component to our main map page and initiate the state for our visible markers.
Now that you have the visible markers being tracked in state you can display those visible markers inside of your application just as you would any other kind of data. Here is a very rough version of how I did it in the demo code.
Adding a locate button
Most mapping applications have some kind of geolocation functionality built in - here we are going to make that user controllable through a “Locate me” button on the map. We will rely on the leaflet-locatecontrol package to handle most of the heavy lifting for us.
First we need to install leaflet-locatecontrol.
Next we need to add the necessary Font Awesome css to our html head element. FYI, this is the recommended installation process, but is not performant. You can override the css to include your own custom icons negating the need for the Font Awesome library.
Now we need to handle actually adding the locate button to the map which we will do with another functional component.
Finally we need to add this component to our main map.js
page.
Wrap up
Woowee! You made it. Congratulations there was lots happening there and I hope you could follow along. By now you should have created a pretty full featured (but unattractive) mapping application. I hope that you found this useful and I would love to hear any feedback or suggestions you have.
If you are looking for more reading Colby Fayock has done a lot of writing on mapping in React. He also has a great course on mapping with Egghead.io as well which I found really helpful.
Happy coding!