Hello !
This post is my first attempt at a technical post.
I am a front-end developer in a GIS company. I create maps and I manage geographic data, in the browser.
Here is an easy way I have found to create an easily maintainable and growing map, while being able to add functionalities in your app.
Prerequisites
This tutorial will use ReactJS, with the latest API Hooks and Context, with LeafletJS and TypeScript.
An understanding of ReactJS will be needed in order to follow this tutorial. I will explain the concepts specifics to Leaflet, Hooks and Context, as much as I can. But as nothing can beat the original, you will find the different documentations here :
Install the dependencies
- First, initiate the react app with CRA : Open a terminal in the folder where you will put your project and type the following command :
npx create-react-app <your-project-name> --template typescript
Go in your newly created folder :
cd <your-project-name>
Add the dependencies for LeafletJS and the plugin React-Leaflet to be able to use Leaflet class as Components.
Include the typing for TypeScript. More than "just" strenghening your code with strong types, they will add easily reached documentation and help you create documentation as part of your code.
npm i react-leaflet leaflet @types/react-leaflet
Setup the map
Setup the basis for a Leaflet application, as described in the Leaflet documentation
- include leaflet in the application : in public/index.html add in the head section, add the leaflet CSS file :
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
crossorigin=""/>
then after the leaflet CSS, add the leaflet JS file :
<!-- Make sure you put this AFTER Leaflet's CSS -->
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
crossorigin=""></script>
Create a component Map
In the src folder, go to the file App.tsx and remove its contents, except the import of React. Create a folder named "components" in src/ and move the App.tsx file in this folder.
We could also keep the declaration of the function App, but I prefer to use the function expression method :
const myFunction = () => {}
in src/components/App.tsx :
import React from 'react';
const App:React.FC = () => {
return (
<LeafletMap />
)
}
Create a new component for your Map, that will be called LeafletMap (but you can call it something else, it is just for distinguing from the Map Component from the Leaflet API).
in src/components/LeafletMap.tsx :
import React from 'react';
import { Map } from 'react-leaflet';
import { LatLngTuple } from 'leaflet';
const defaultLatLng: LatLngTuple = [48.865572, 2.283523];
const zoom:number = 8;
const LeafletMap:React.FC = () => {
return (
<Map>
</Map>
)
}
The Map will need a few basics properties, its center, and its zoom. It will also need a basic width and height, necessary to see the map.
in src/components/LeafletMap.tsx :
import React from 'react';
import { Map } from 'react-leaflet';
import { LatLngTuple } from 'leaflet';
const defaultLatLng: LatLngTuple = [48.865572, 2.283523];
const zoom:number = 8;
const LeafletMap:React.FC = () => {
return (
<Map id="mapId"
center={defaultLatLng}
zoom={zoom}>
</Map>
)
}
export default LeafletMap;
in src/index.css :
...
// those values can ofcourse be adapted depending on the kind of app you are
// creating, but they must be set in order to see the map
#mapId {
height:100vh;
width:100vw;
}
Include a basemap
You have now the base of your map, but you still wont see much in your app.
You first need to include a basemap. Fortunately, even if you don't have an API or a ressource, Leaflet allows you to use OpenStreetMap
In your Map component, add the TileLayer component with the url reference to the OpenStreetMap basemap :
...
<Map id="mapId"
center={defaultLatLng}
zoom={zoom}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution="© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors">
</TileLayer>
</Map>
...
The components included in the React-Leaflet library use, as props, the same options than the Leaflet class they are encapsulating.
Your map is now ready to be seen. Great !
Open "http://localhost:3000/" in your browser :
Of course, you don't have much to show apart from the basemap, but it is a start, right ?
Now, it is time to add some interaction.
setup the context API
First, we will setup the state management that will be need to interact with the map.
Create the Context Component
In src/components, add a new folder "context".
In this folder, create a new file "LayerContext".
The Context API of React is a solution really appropriate for managing the state of your app. If the app grows in size, and impact, Redux could be used for the same action, but in this case, Context is perfectly efficient.
Mostly, what's Context is allowing us to do is effectively manage the layers and features that will be add to the map from everywhere in the app. It will simplify the creation of menus, overlay or others buttons.
React-Leaflet is actually based on this React Context API.
Start creating your context object using the method React.createContext();
In src/components/context/LayerContext.tsx :
import React from 'react';
const LayerContext:any = React.createContext({});
Then let's write the basis for the Context Provider which will wrap the Map Component, and provide all the states and functions included in it :
...
\\ under the declaration of the LayerContext, declare the Provider Component
const LayerContextProvider = ({ children }: any) => {
const defaultValue = {
}
return (
<LayerContext.Provider value={defaultValue}>
{children}
</LayerContext.Provider>
)
}
\\ and export both objects
export { LayerContext, LayerContextProvider };
add a state using the Hook method, it will hold the point that we will add to the Map.
...
const LayerContextProvider = ({ children }: any) => {
const [point, setPoint] = useState<LatLng>([0, 0]);
const defaultValue = {
point,
setPoint
}
Add the Provider around the Map Component
Go back to your App Component and surround the LeafletMap component with your newly-created LayerContextProvider. You will be able to manipulate your geometries from anywhere in the App, if it is in the Provider.
const App: React.FC = () => {
return (
<LayerContextProvider>
<LeafletMap />
</LayerContextProvider>
)
}
In your Map component, add the LayerGroup to receive your point
const { point } = useContext(LayerContext);
return (
<Map id="mapId"
center={defaultLatLng}
zoom={zoom}>
<LayerGroup>
{point}
</LayerGroup>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution="© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors">
</TileLayer>
</Map>
)
...
setup the onClick hook
In your component folder, create a new customHooks folder.
The Rules of Hooks informs us that all hooks must start with "use".
In this folder, create a new useAddMarker.tsx for your custom hooks.
import { useContext, useEffect } from 'react';
import { useLeaflet, Marker } from 'react-leaflet';
import L, { LeafletMouseEvent } from 'leaflet';
import { LayerContext } from '../context/LayerContext';
// the hook Effect will be activated by the click on the button
function useAddMarker(selected:boolean) {
// The hook 'useLeaflet' is provided by the react-leaflet library.
// This hook allow to access to the Leaflet Context and its variables.
// It is a simple way to access the map and its content.
const { map } = useLeaflet();
// the hook useContext is used to access to the previously defined LayerContext.
const { setPoint } = useContext(LayerContext);
// add a state to activate the Event
const [activate, setActivate] = useState(selected);
// define the MouseEvent with the useCallback hook
const markerEvent = useCallback(
(e: LeafletMouseEvent) => {
// if you want to use any event,
// be sure that the default is disabled.
e.originalEvent.preventDefault();
// create your Marker with the react leaflet component Marker
setPoint(<Marker position={e.latlng} />);
e.originalEvent.stopPropagation();
}, [setPoint]);
// activate the EventHandler with the useEffect handler
useEffect(
() => {
map?.doubleClickZoom.disable()
if (activate === true) {
map?.on('dblclick', markerEvent);
}
return () => {
map?.off('dblclick', markerEvent);
}
}, [map, activate, markerEvent]
)
}
export default useAddMarker;
use a button to activate the hook
Now that every functionnality are created, you just need to connect everything.
Create a button that will activate the AddMarker Hook.
In src/components, create a new AddMarkerButton Component. It will be used to add the function to any
In src/components/AddMarkerButton.tsx :
import useAddMarker from './customHooks/useAddMarker';
interface Props {}
const AddMarkerButton: React.FC<Props> = (props) => {
const { setActivate, activate } = useAddMarker(false);
return <button onClick={() => setActivate(!activate)}>Add Points</button>
}
export default AddMarkerButton;
In src/index.css
button{
position: absolute;
top: 50px;
left: 150px;
z-index:2000;
width:100px;
height:50px;
color:gray;
}
Finish by adding your button component to the Map
...
<Map id="mapId"
center={defaultLatLng}
zoom={zoom}>
<AddMarkerButton />
<LayerGroup>
{point}
</LayerGroup>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution="© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors">
</TileLayer>
</Map>
...
Conclusion
You now have a ready to use and complete map.
You have currently only one marker that you can change at every double click. But it could be a List of Marker, creating a more interesting Layer.
You could now add some data, using the context to organise any actions with the geometry and the map.
I have recently found that GraphQL, using Hasura for the PostGreSQL/PostGIS implementation, goes really well with this logical.
That's it!
Open "http://localhost:3000/" in your browser :
Latest comments (10)
Help me!)
Module '"../../node_modules/react-leaflet/types"' has no exported member 'Map'
Okay but I can't if you don't tell me how
Thank u for answer :) I resolved error.
Your post helped me.
Ah okay great you are welcome
Axelle. Excelente publicaciรณn!!
Gracias por compartirlo. Saludos desde Argentina
Thank you !
Thanks for the amazing content. It helped me get started, I'm trying to make an Ionic5 application with React support that includes leaflet maps. I've been using leafletJs in vanilla JS and in ReactJS before, but this is my first time to use it in typescript and in Ionic. Wish me luck (Y)
Good Luck ! :) I don't know Ionic, I would be interested to know more about how it went
Amazing article, it helped me a lot with starting my univeristy project using this exact stack!
I think one thing you have missed at the end of useAddMarker hooks is return { activate, setActivate } as I was getting error when trying to destructure the hook in the AddMarkerButton component.
You're welcome, happy to know it helped
oh yes probably, I created this article based on an app I created for my work, I simplified a lot, I may have deleted too much, or made mistakes in copying the function