Create Stunning Maps Effortlessly - Master React Leaflet with TypeScript!
Add to your RSS feed30 September 202414 min readTable of Contents
In today’s web development landscape, integrating maps into applications has become increasingly popular. Whether you’re building a location-based service, a travel app, or a simple data visualization project, having a reliable mapping library is essential. This article will guide you through creating a simple map using React, TypeScript, and LeafletJS. By the end, you'll have a functional map application and a solid understanding of how to leverage React Leaflet for your projects.
What is React Leaflet?
React Leaflet is a powerful library that provides React components for Leaflet, a popular open-source JavaScript library for interactive maps. It allows developers to easily integrate and customize maps in React applications while taking advantage of the flexibility and capabilities of LeafletJS. With React Leaflet, you can create responsive, interactive maps that can display various geographic data layers and user interactions.
Setting Up Your Project
1. Create a New React Project
First, create a new React project using Vite with TypeScript template:
2. Install Required Packages
Next, install the necessary packages, including React Leaflet and Leaflet:
Also, you will need to install the types for Leaflet:
3. Include Leaflet CSS
To properly display the map, you need to include Leaflet’s CSS. Open the src/App.tsx file and add the following line:
1 import 'leaflet/dist/leaflet.css';
Creating Your First Map
1. Set Up the Map Component
Now, let’s create a simple map component. Create a new file called MapComponent.tsx in the src directory:
1 // src/MapComponent.tsx23 import React from 'react';4 import { MapContainer, TileLayer } from 'react-leaflet';5 import 'leaflet/dist/leaflet.css';67 const MapComponent: React.FC = () => {8 return (9 <MapContainer center={[1.3521, 103.8198]} zoom={13} style={{ height: '100vh', width: '100%' }}>10 <TileLayer11 url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'12 attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'13 />14 </MapContainer>15 );16 };1718 export default MapComponent;
2. Update Your App Component
Next, import the MapComponent into your main App.tsx file and render it:
1 // src/App.tsx23 import React from 'react';4 import MapComponent from './MapComponent';56 const App: React.FC = () => {7 return (8 <div>9 <h1>My Simple Map</h1>10 <MapComponent />11 </div>12 );13 };1415 export default App;
- Run Your Application
Now, you can run your application to see the map:
Open your browser and navigate to http://localhost:5173/. You should see a simple map centered on Singapore!
Let’s style the map. Open index.css, clear its contents, and add the following class for .leaflet-container:
1 .leaflet-container {2 width: 100vw;3 height: 80vh;4 }
Adding Markers and Popups
1. Install Marker Component
To add markers and popups, you will need the Marker and Popup components from React Leaflet. Modify your MapComponent as follows:
1 // src/MapComponent.tsx23 import React from 'react';4 import L from 'leaflet';5 import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';6 import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png';7 import markerIcon from 'leaflet/dist/images/marker-icon.png';8 import markerShadow from 'leaflet/dist/images/marker-shadow.png';910 import 'leaflet/dist/leaflet.css';1112 // Default marker icon13 delete L.Icon.Default.prototype._getIconUrl;14 L.Icon.Default.mergeOptions({15 iconRetinaUrl: markerIcon2x,16 iconUrl: markerIcon,17 shadowUrl: markerShadow,18 });1920 const MapComponent: React.FC = () => {21 return (22 <MapContainer center={[1.3521, 103.8198]} zoom={13} style={{ height: '100vh', width: '100%' }}>23 <TileLayer24 url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'25 attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'26 />27 <Marker position={[1.3521, 103.8198]}>28 <Popup>29 A pretty CSS3 popup. <br /> Easily customizable.30 </Popup>31 </Marker>32 </MapContainer>33 );34 };3536 export default MapComponent;
2. Test Your Markers
Run your application again. You should now see a marker on the map, and clicking it will open a popup.
Add Shapes to the Map
Here’s how to add different shapes like polygons, circles, and rectangles to your map. Modify your MapComponent.tsx:
1 // src/MapWithShapes.tsx23 import React from 'react';4 import { MapContainer, TileLayer, Polygon, Circle, Rectangle } from 'react-leaflet';5 import 'leaflet/dist/leaflet.css';67 const MapWithShapes: React.FC = () => {8 // Define positions for Polygon, Circle, and Rectangle9 const polygonPositions = [10 [51.51, -0.12],11 [51.51, -0.1],12 [51.52, -0.1],13 ];1415 const circleCenter = [51.505, -0.09];16 const rectangleBounds = [17 [51.49, -0.08],18 [51.5, -0.06],19 ];2021 return (22 <MapContainer center={[51.505, -0.09]} zoom={13} style={{ height: '100vh', width: '100%' }}>23 <TileLayer24 url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'25 attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'26 />2728 {/* Add Polygon */}29 <Polygon positions={polygonPositions} color='purple' />3031 {/* Add Circle */}32 <Circle center={circleCenter} radius={500} color='blue' />3334 {/* Add Rectangle */}35 <Rectangle bounds={rectangleBounds} color='green' />36 </MapContainer>37 );38 };3940 export default MapWithShapes;
- Explanation of the Code
Polygon:
- A polygon is drawn by specifying an array of latitude and longitude points in the positions prop.
- In the example above, a triangle-shaped polygon is created with three sets of coordinates.
- You can change the color property to any valid CSS color.
Circle:
- A circle is drawn by providing a center prop with a latitude and longitude and a radius (in meters).
- In this case, a blue circle with a 500-meter radius is drawn.
Rectangle:
- A rectangle is defined by two opposite corners using the bounds prop.
- The rectangle is rendered as a green box on the map.
- Run Your Application
To see the shapes on your map, run the application:
- Customizing Shapes
You can further customize the shapes by adjusting properties like:
- Color: Modify the color prop to change the border color of the shape.
- Fill color: Use the fillColor prop to set the inside color of the shape.
- Stroke weight: Adjust the border thickness with the weight prop.
- Opacity: Use fillOpacity and opacity to control transparency.
Example:
1 <Circle2 center={circleCenter}3 radius={500}4 color='red'5 fillColor='pink'6 fillOpacity={0.5}7 weight={2}8 />
With React Leaflet, adding shapes such as polygons, circles, and rectangles to your maps is simple and efficient.
Common React Leaflet Events
Let’s explore how you can use React Leaflet events in your project, including how to listen for and respond to events like map clicks, marker drags, and zoom changes.
Here are some of the most commonly used events in React Leaflet:
- Map Events:
onClick
,onZoom
,onMove
,onLoad
- Marker Events:
onClick
,onDragEnd
,onMouseOver
,onPopupOpen
- Layer Events:
onAdd
,onRemove
,onPopupOpen
Each component (such as MapContainer
, Marker
, Polygon
) can listen for different events
depending on its type. You can pass event handlers directly to these components as props.
Example: Handling Map Click and Marker Events
Let’s create an example where we listen to map clicks and move markers to the changes.
Create MapWithEvents.tsx component:
1 import { useState } from 'react';2 import { Marker, Popup, useMapEvents } from 'react-leaflet';34 const MapWithEvents = () => {5 const [position, setPosition] = useState([1.3521, 103.8198]);6 const map = useMapEvents({7 click(e) {8 setPosition(e.latlng);9 map.flyTo(e.latlng, map.getZoom());10 },11 });1213 return position === null ? null : (14 <Marker position={position}>15 <Popup>You are here</Popup>16 </Marker>17 );18 };1920 export default MapWithEvents;
Explanation:
- onClick Event: Captures the latitude and longitude of the point where the user clicks on the map and updates the state with that information.
Display the Map
Finally, you need to render the MapWithShapes component in your main MapComponent.tsx file:
1 import React, { useState } from 'react';2 import L from 'leaflet';3 import { MapContainer, TileLayer, Marker, Popup, Rectangle, Circle, Polygon } from 'react-leaflet';4 import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png';5 import markerIcon from 'leaflet/dist/images/marker-icon.png';6 import markerShadow from 'leaflet/dist/images/marker-shadow.png';7 import MapWithEvents from './MapWithEvents';89 import 'leaflet/dist/leaflet.css';1011 // Default marker icon12 delete L.Icon.Default.prototype._getIconUrl;13 L.Icon.Default.mergeOptions({14 iconRetinaUrl: markerIcon2x,15 iconUrl: markerIcon,16 shadowUrl: markerShadow,17 });1819 // Define positions for Polygon, Circle, and Rectangle20 const polygonPositions = [21 [1.3521, 103.8198],22 [1.3521, 103.8197],23 [1.3521, 103.8196],24 ];2526 const circleCenter = [1.3521, 103.8198];27 const rectangleBounds = [28 [1.3521, 103.8197],29 [1.3521, 103.8196],30 ];3132 const MapComponent: React.FC = () => {33 const [mapClickPosition, setMapClickPosition] = useState<string | null>(null);34 const [zoomLevel, setZoomLevel] = useState<number>(13);3536 // Handler for map click37 const handleMapClick = (event) => {38 const { lat, lng } = event.latlng;39 console.log('handleMapClick');40 setMapClickPosition(`Latitude: ${lat}, Longitude: ${lng}`);41 };4243 // Handler for zoom change44 const handleZoomEnd = (event) => {45 console.log('handleZoomEnd', event);46 setZoomLevel(event.target.getZoom());47 };4849 return (50 <MapContainer51 center={[1.3521, 103.8198]}52 style={{ height: '100vh', width: '100%' }}53 zoom={zoomLevel}54 onClick={(e) => handleMapClick(e)} // Map click event55 onZoomEnd={handleZoomEnd} // Zoom end event56 >57 <TileLayer58 url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'59 attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'60 />61 <Marker position={[1.3521, 103.8198]}>62 <Popup>63 A pretty CSS3 popup. <br /> Easily customizable.64 </Popup>65 </Marker>6667 {/* Add Polygon */}68 <Polygon positions={polygonPositions} color='purple' />6970 {/* Add Circle */}71 <Circle center={circleCenter} radius={500} color='blue' />7273 {/* Add Rectangle */}74 <Rectangle bounds={rectangleBounds} color='green' />75 <MapWithEvents />76 </MapContainer>77 );78 };7980 export default MapComponent;
List GeoJSON Location Data in the Map
Let’s display a list of all the parks in Singapore using GeoJSON location data. You can download or copy the list from here - https://data.gov.sg/datasets/d_14d807e20158338fd578c2913953516e/view. If you choose to download it, rename the file to Park-Facilities.json.
Now, let’s create the Parks.tsx component and render it inside the MapComponent.tsx
1 // Parks.tsx23 import { useEffect } from 'react';4 import { useMap } from 'react-leaflet';56 import SingaporesParks from './Park-Facilities.json';78 const Parks = () => {9 const map = useMap();10 useEffect(() => {11 console.log('map', map);12 if (!map) {13 return;14 }15 const parksInSingaporeGeoJson = new L.GeoJSON(SingaporesParks);16 parksInSingaporeGeoJson.addTo(map);17 }, [map]);18 return <></>;19 };20 export default Parks;
Connect it to MapComponent
1 import React, { useState } from 'react';2 import L from 'leaflet';3 import { MapContainer, TileLayer, Marker, Popup, Rectangle, Circle, Polygon } from 'react-leaflet';4 import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png';5 import markerIcon from 'leaflet/dist/images/marker-icon.png';6 import markerShadow from 'leaflet/dist/images/marker-shadow.png';7 import MapWithEvents from './MapWithEvents';89 import 'leaflet/dist/leaflet.css';10 import Parks from './Parks';1112 // Default marker icon13 delete L.Icon.Default.prototype._getIconUrl;14 L.Icon.Default.mergeOptions({15 iconRetinaUrl: markerIcon2x,16 iconUrl: markerIcon,17 shadowUrl: markerShadow,18 });1920 // Define positions for Polygon, Circle, and Rectangle21 const polygonPositions = [22 [1.3521, 103.8198],23 [1.3521, 103.8197],24 [1.3521, 103.8196],25 ];2627 const circleCenter = [1.3521, 103.8198];28 const rectangleBounds = [29 [1.3521, 103.8197],30 [1.3521, 103.8196],31 ];3233 const MapComponent: React.FC = () => {34 const [mapClickPosition, setMapClickPosition] = useState<string | null>(null);35 const [zoomLevel, setZoomLevel] = useState<number>(13);3637 // Handler for map click38 const handleMapClick = (event) => {39 const { lat, lng } = event.latlng;40 console.log('handleMapClick');41 setMapClickPosition(`Latitude: ${lat}, Longitude: ${lng}`);42 };4344 // Handler for zoom change45 const handleZoomEnd = (event) => {46 setZoomLevel(event.target.getZoom());47 };4849 return (50 <MapContainer51 center={[1.3521, 103.8198]}52 style={{ height: '100vh', width: '100%' }}53 zoom={zoomLevel}54 onClick={(e) => handleMapClick(e)} // Map click event55 onZoomEnd={handleZoomEnd} // Zoom end event56 >57 <TileLayer58 url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'59 attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'60 />61 <Marker position={[1.3521, 103.8198]}>62 <Popup>63 A pretty CSS3 popup. <br /> Easily customizable.64 </Popup>65 </Marker>6667 {/* Add Polygon */}68 <Polygon positions={polygonPositions} color='purple' />6970 {/* Add Circle */}71 <Circle center={circleCenter} radius={500} color='blue' />7273 {/* Add Rectangle */}74 <Rectangle bounds={rectangleBounds} color='green' />75 <MapWithEvents />76 <Parks />77 </MapContainer>78 );79 };8081 export default MapComponent;
Loading all of these markers took quite some time because Singapore has a large number of parks 🤩
Render names on Markers
To render the names on the parks we need to modify the GeoJSON function:
1 const parksInSingaporeGeoJson = new L.GeoJSON(SingaporesParks, {2 onEachFeature: (feature = {}, layer) => {3 const { properties = {} } = feature;4 const { Name } = properties;5 if (!Name) {6 return;7 }8 layer.bindPopup(`<p>${Name}</p>`);9 },10 });
Now when we click on the marker, we can see the name of the park, or his id number
Costumize the Markers With Custom Icons
You can create a custom icon by using L.icon(). This function allows you to set the URL for the icon image, size, anchor points, and more.
Download the icon:
Modify the Parks.tsx component:
1 import { useEffect } from 'react';2 import { useMap } from 'react-leaflet';34 import ParkIcon from './park-icon.png';5 import SingaporesParks from './Park-Facilities.json';67 const Parks = () => {8 const map = useMap();910 useEffect(() => {11 if (!map) {12 return;13 }1415 const parkIcon = L.icon({16 iconUrl: ParkIcon, // URL to your custom icon17 iconSize: [24, 24], // Size of the icon [width, height]18 iconAnchor: [16, 32], // Point of the icon that will correspond to marker's location19 popupAnchor: [0, -32], // Point from which the popup should open relative to the iconAnchor20 });2122 const parksInSingaporeGeoJson = new L.GeoJSON(SingaporesParks, {23 pointToLayer: (feature = {}, latlng) => {24 return L.marker(latlng, {25 icon: parkIcon,26 });27 },28 onEachFeature: (feature = {}, layer) => {29 const { properties = {} } = feature;30 const { Name } = properties;31 if (!Name) {32 return;33 }34 layer.bindPopup(`<p>${Name}</p>`);35 },36 });37 parksInSingaporeGeoJson.addTo(map);38 }, [map]);39 return <></>;40 };41 export default Parks;
The Explanation:
L.icon()
: This method is used to define a custom Leaflet icon.
- iconUrl: The URL or path to the image file for the custom icon.
- iconSize: Defines the width and height of the icon.
- iconAnchor: Specifies which point of the icon image corresponds to the exact geographical location (usually the bottom center of the image).
- popupAnchor: Determines the position of the popup relative to the icon.
pointToLayer
: A function that is called for each point feature in the GeoJSON file. It converts each park location (latlng) into a marker with the custom parkIcon.
Toggle Between Different Lyers With LayersControl
To add a Layers Control to a React Leaflet map, you can use the LayersControl component provided by React Leaflet. This allows you to toggle between different layers on the map.
Setup the Layers Control
Now, let’s set up different tile layers (like a satellite view, street view, etc.) and markers as layers that can be toggled using LayersControl.
Import Required Components
You will need LayersControl, LayerGroup, and optionally Marker or any other components for different layers.
1 import { LayersControl, LayerGroup } from 'react-leaflet';
1 // MapComponent.tsx23 import React, { useState } from 'react';4 import L from 'leaflet';5 import {6 MapContainer,7 TileLayer,8 Marker,9 Popup,10 Rectangle,11 Circle,12 Polygon,13 LayersControl,14 LayerGroup,15 } from 'react-leaflet';16 import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png';17 import markerIcon from 'leaflet/dist/images/marker-icon.png';18 import markerShadow from 'leaflet/dist/images/marker-shadow.png';19 import MapWithEvents from './MapWithEvents';2021 import 'leaflet/dist/leaflet.css';22 import Parks from './Parks';2324 // Default marker icon25 delete L.Icon.Default.prototype._getIconUrl;26 L.Icon.Default.mergeOptions({27 iconRetinaUrl: markerIcon2x,28 iconUrl: markerIcon,29 shadowUrl: markerShadow,30 });3132 // Define positions for Polygon, Circle, and Rectangle33 const polygonPositions = [34 [1.3521, 103.8198],35 [1.3521, 103.8197],36 [1.3521, 103.8196],37 ];3839 const circleCenter = [1.3521, 103.8198];40 const rectangleBounds = [41 [1.3521, 103.8197],42 [1.3521, 103.8196],43 ];4445 const MapComponent: React.FC = () => {46 const [mapClickPosition, setMapClickPosition] = useState<string | null>(null);47 const [zoomLevel, setZoomLevel] = useState<number>(13);4849 // Handler for map click50 const handleMapClick = (event) => {51 const { lat, lng } = event.latlng;52 console.log('handleMapClick');53 setMapClickPosition(`Latitude: ${lat}, Longitude: ${lng}`);54 };5556 // Handler for zoom change57 const handleZoomEnd = (event) => {58 setZoomLevel(event.target.getZoom());59 };6061 const { BaseLayer, Overlay } = LayersControl;6263 return (64 <MapContainer65 center={[1.3521, 103.8198]}66 style={{ height: '100vh', width: '100%' }}67 zoom={zoomLevel}68 onClick={(e) => handleMapClick(e)} // Map click event69 onZoomEnd={handleZoomEnd} // Zoom end event70 >71 <LayersControl position='topright'>72 {/* Base Layers */}73 <BaseLayer checked name='OpenStreetMap'>74 <LayerGroup>75 <TileLayer76 url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'77 attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'78 />79 <Marker position={[1.3521, 103.8198]}>80 <Popup>81 A pretty CSS3 popup. <br /> Easily customizable.82 </Popup>83 </Marker>8485 {/* Add Polygon */}86 <Polygon positions={polygonPositions} color='purple' />8788 {/* Add Circle */}89 <Circle center={circleCenter} radius={500} color='blue' />9091 {/* Add Rectangle */}92 <Rectangle bounds={rectangleBounds} color='green' />93 <MapWithEvents />94 {/* <Parks /> */}95 </LayerGroup>96 </BaseLayer>97 <BaseLayer name='Satellite'>98 <TileLayer99 url='https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'100 attribution='© <a href="https://opentopomap.org/">OpenTopoMap</a> contributors'101 />102 </BaseLayer>103 <Overlay checked name='Marker'>104 <Marker position={[1.3521, 103.8198]} />105 </Overlay>106 </LayersControl>107 </MapContainer>108 );109 };110111 export default MapComponent;
Explanation of LayersControl:
LayersControl: This component wraps all layers you want to control. You can switch between base layers and toggle overlays on or off.
- BaseLayer: Represents layers that switch between each other. Only one base layer can be active at a time.
- Overlay: Represents layers that can be turned on or off independently from the base layers.
Base Layers:
- We define two base layers: one using OpenStreetMap tiles, and another using a satellite view. Only one of these can be visible at a time.
- The checked attribute on a BaseLayer makes it the default layer when the map loads.
Overlays:
- The Overlay layer is for additional features that can be toggled on and off independently of the base layers.
- In this case, we add a marker as an overlay.
LayerGroup:
- Groups multiple layers together.
- Inside the LayerGroup, we have a several markers, each at different coordinates. These markers will be treated as one group.
- The Overlay component here wraps the LayerGroup, allowing you to toggle the visibility of the grouped markers via the control or a button.
You can find the code from the article on the github.
Conclusion
In this article, we covered how to create a simple map using React, TypeScript, and LeafletJS. You learned how to set up your project, create a map component, add markers and popups, and customize your map’s appearance. With these skills, you can further expand your application by integrating additional features like user location tracking, clustering, or displaying geographic data.
By mastering React Leaflet, you’re well on your way to building engaging and interactive map-based applications that enhance user experiences. Happy coding!