How to create the base of a map with ReactJS, Leaflet and TypeScript
Tags: react, tutorial, gis, showdev
Published: 2020-03-13 15:26:55

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 :

1. [ReactJS](https://reactjs.org/)
  * [React Hooks](https://reactjs.org/docs/hooks-intro.html)
  * [React Context](https://reactjs.org/docs/context.html)
2. [Leaflet](https://leafletjs.com/)
3. [React-Leaflet](https://react-leaflet.js.org/)

## 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 :
```html
<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 :
```html
 <!-- 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 :
```typescript
const myFunction = () => {}
```

in src/components/App.tsx :
```typescript
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 :
```typescript
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 :
```typescript
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 : 
```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 :

```typescript
...
    <Map id="mapId"
          center={defaultLatLng}
          zoom={zoom}>
      <TileLayer
         url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
         attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>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 :
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xdqdmo3mabdxadjnb9rb.PNG)

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 :
```typescript
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 :

```typescript
... 
\\ 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.

```typescript
... 
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.

```typescript
const App: React.FC = () => {

    return (
        <LayerContextProvider>
            <LeafletMap />
        </LayerContextProvider>
    )
}
```

In your Map component, add the LayerGroup to receive your point

```typescript
    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="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors">
      </TileLayer>
   </Map>
  )
...
```

## setup the onClick hook

In your component folder, create a new customHooks folder.
The [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html) informs us that all hooks must start with "use".
In this folder, create a new useAddMarker.tsx for your custom hooks.

```typescript
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 :
```typescript
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
````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

```typescript
...
    <Map id="mapId"
         center={defaultLatLng}
         zoom={zoom}>
            <AddMarkerButton />
            <LayerGroup>
                {point}
            </LayerGroup>
            <TileLayer
                url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
                attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>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 :
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/csmlsvxozmjmipwku6g3.PNG)