Creating map components in  ReasonReact with bindings for LeafletJS
Tags: react, reason, javascript
Published: 2021-01-10 08:51:22

The ReasonML community is expanding everyday with a growing number of [users](https://reasonml.github.io/en/users-of-reason) and many more are getting inspired to adopt. Most of these adopters have been previously writing code in Javascript and there are some compelling reasons for their migration to ReasonML.

A few years ago, with the advent of React, JS developers were opening up to concepts of functional programming and its benefits for concurrency. While Typescript was still coming of age with its roots in JS, a strongly typed language was needed for web development which is aligned with fundamentals of functional programming. ReasonML was built right for this purpose, providing the best of JS and OCaml environments. Through OCaml’s FFI, ReScript provided excellent bridges between these environments which enabled developers to build some fine projects using ReasonReact. This was a key factor behind the popularity of ReasonReact.

The JS environment is rich with many useful libraries, which ReasonML developers frequently feel the need to use. As a result writing efficient bindings becomes crucial. Bindings are APIs that allow developers to use libraries from other environments. OCaml’s FFI allows developers to use JS libraries through these bindings ([ReScript docs](https://rescript-lang.org/docs/manual/v8.0.0/external)). Even though ReScript keeps on adding libraries to their offering, a myriad of libraries are still unavailable. 

This article will demonstrate utilization of one such library called [LeafletJS](https://leafletjs.com), for which bindings will be written and used in accordance with React philosophy. The aim here is to display a map and place a marker on it through React components using functions from **LeafletJS**. The source code for this exercise is available in [bs-leaflet-cmp](https://github.com/ashutheone/bs-leaflet-cmp), which can also be used for initial setup.

Having identified the desired library, next step is to include it in the project as recommended [here](https://leafletjs.com/examples/quick-start), by adding following code to `index.html`

```
  <link rel="stylesheet"
href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin="" />
  <script
src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
crossorigin=""></script>
```

First goal is to create a component which renders a map. To attain this, first step is to write some bindings for the required functions from **LeafletJS**.  Here is a sample binding, which can also be found in [*MapBinding.re*](https://github.com/ashutheone/bs-leaflet-cmp/blob/main/src/js-bindings/MapBinding.re):
```
[@bs.scope "L"] [@bs.new]
external createMap: (string, Map.options) => Map.t = "map";
```
This accepts a string, which is the DOM ID of a *div*, and optionally an object literal for options. It instantiates and returns a `map` object of type `Map.t`.

Writing bindings also involves declaring the ReasonML types compatible with target APIs, as shown above. To facilitate this, ReasonML has some [shared bidirectional data types](https://rescript-lang.org/docs/manual/v8.0.0/shared-data-types), thus omitting the need for data-converters.

Next step is to create a React component `MapCmp` with render props which enables all subsequent *map-elements* to use the `map` object. This component can be found at [*MapCmp.re*](https://github.com/ashutheone/bs-leaflet-cmp/blob/main/src/map-components/MapCmp.re). It is responsible for instantiating `map` object for the rendered *map-div* followed by creating and adding a tilelayer to the map and rendering children. The `useEffect0` for this component is written in following manner to accomplish this:

```
React.useEffect0(() => {
    let mapObj = MapBinding.createMap(
                   mapOptions.map_elem_id,
                   mapOpts
                 );
    let tileLayer =
      MapBinding.createTileLayer(
        "https://tile.thunderforest.com/neighbourhood/
        {z}/{x}/{y}.png?apikey=<ApiKey>",
        {
          attribution: "Maps &copy;
            <a href='https://www.thunderforest.com/'>
              Thunderforest
            </a>
            , Data &copy;
            <a href='http://www.openstreetmap.org/copyright'>
              OpenStreetMap contributors
            </a>",
          minZoom: 11,
          maxZoom: 15,
        },
      );
    MapBinding.addLayerToMap(tileLayer, mapObj)->ignore;
    None;
  });
```

After creating a component to render a map, next goal is to create a component for placing marker. First step towards that is writing a binding for a function to create marker. This binding can be found in [*MarkerBinding.re*](https://github.com/ashutheone/bs-leaflet-cmp/blob/main/src/js-bindings/MarkerBinding.re):

```
external createMarker:
  (~pos: LatLng.t,
   ~opts: option(Marker.options)=?,
   unit) => Marker.t = "marker";
```

Next step would be writing a component which creates and adds a marker on the given map. To achieve this, bindings are used in following manner in the `useEffect0` for this component:

```
React.useEffect0(() => {
    let marker =
      MarkerBinding.createMarker(
        ~pos=marker_props.location,
        ~opts=marker_props.marker_options,
        (),
      );
    MarkerBinding.addMarkerToMap(marker, map) |> ignore;
    dispatch(SetMyMarker(Some(marker)));
    Some(() => MarkerBinding.removeMarkerFromMap(
                 marker, map
               )->ignore
    );
  });
```

Important to note that it removes marker from the map upon cleanup. There are other features and effects which can be added to the marker component. For example, if the marker position is updated in props, then a `useEffect` can be added to update marker location in accordance:

```
  React.useEffect1(
    () => {
      switch (state.marker) {
      | Some(marker) =>
        MarkerBinding.setMarkerLatLng(
          marker, marker_props.location
        ) |> ignore
      | _ => ()
      };
      None;
    },
    [|marker_props.location|],
  );
```

The usage of `MapCmp` and `MarkerCmp` is shown in [*ExampleComponent.re*](https://github.com/ashutheone/bs-leaflet-cmp/blob/main/src/ExampleComponent.re) in the repo. Note that the repo contains more such components and features. These components are used as shown below 

```
<MapCmp mapOptions={
          map_elem_id: "map_div",
          options: {
            center: {lat: 13.0, lng: 77.60},
            zoom: 12,
          }}>
    {map => <MarkerCmp
               map
               marker_props={
                 location: { lat: 13.0,lng: 77.60},
                 marker_options: None}>
           </MarkerCmp>}
</MapCmp>
```
The generated result looks like this:

![leafletjs map with thunderforest](https://dev-to-uploads.s3.amazonaws.com/i/pm2pnujlzi15l2lutxki.png)

The aim of this article was to share an approach for using ReasonML-JS bindings through react components. This makes code very scalable and structured, which was demonstrated with the help of a use-case involving maps. Though the bindings and components presented here are most basic in nature and were intended to highlight this concept, with careful design they have a potential of scaling to smoothly handle complex use-cases.