Skip to content
Chris Marx edited this page Sep 29, 2021 · 48 revisions

Welcome to the foss4g-2021-react-mapbox wiki!

Welcome to live coding with mapbox and react

What we're going to build

  • Before I even get into introductions, if you're trying to decide what talk to go to today, here's what we're building. It's standard map app, with a couple of layers to give us something to work with, a layer panel for turning things on and off, and a custom home button component, Two things that I've added to more maps than I can count. That might not sound like too much, but we're going to be focusing on how to build an app like this from scratch, and also on react, typescript, and the component driven design, as well as how to create libraries of reusable components, and even using those components as web components in other application that don't even use react. I'm using deck.gl for these fun map visualizations, mainly because it's just so easy, especially since we're already going to be using react-map-gl, which is definitely the easiest way to use mapbox in react. It's from Uber for mapbox, now all part of the opem source vis.gl ecosystem. Also because this is FOSS4G, I'll be using the maplibre open source mapbox fork, and quickly show how that can a drop in replacement for mapbox.
  • Note that we're using mapbox (or maplibre), and I use mapbox more than anything else, because we love the support for fancy vector tile layers, which can be either uploaded to mapbox for hosting, or created right out of postgres. For instance, here's a map we're working on ( show tab with app (without url) with header removed and switched to subnational), you'd probably not get away with loading a hi-res, detailed world country layer with all the subregions as pure geojson. But the focus of this talk is on architecting apps in general, specifically, using the microfrontend approach that was pioneered by angular, and popularized by react and vue, in which as much as possible, each feature/responsibility on the site is broken off into it's own chunk or component. In this example, we can see other reusable components you might want to develop for you map libraries, like a geocoder control, floating map window, button layer toggles, the list is endless. And here's the kind of coding we're doing with react and typescript.

URL for github, put it in chat, it has link to codesandbox.io for running

Introduction

  • Twitter - you can follow up with me on twitter, ask any questions, etc. I'll also be taking breaks about every 15 minutes or so to address questions during the talk
  • I work here, at ZevRoss Spatial Analysis, we're a consulting firm in NY, we do--- , you can check it out-
  • I however am not in NY, when it appeared we'd be WorkingFromHome for the seeable future, we escaped to someplace warm :)

Setup

  1. Git clone the blank repo

  2. Setup create react app blank project - https://create-react-app.dev/

    cd foss4g-2021-react-mapbox  #or sandbox1, etc
    #NOTE you could use a "." to build in the current folder, but we actually will need this to be in it's own project folder, so that we can add a separate library folder, and component playground
    npx create-react-app deck-layers-map --use-npm --template typescript
    cd deck-layers-map
    
  3. Switch to typescript talk, it takes a while

  4. Add the dependency for react-map-gl - https://visgl.github.io/react-map-gl/

    npm install react-map-gl
    
  5. Start by create a map component. Autogenerate the component with this https://github.com/arminbro/generate-react-cli:

    npx generate-react-cli component BackdropMap
    

    We're using typescript, react testing library, stories, stylesheets just plain css (i like sass), etc.

  6. In general, it's a good idea to not name your components with the same name as built in classes in Javascript, so typically you should avoid naming things "Map", "Set", "Array", "WeakMap", "Date" etc. - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects, it will give you odd errors, and will make it so you have to alias your classes if you want to also use those things in your app.

  7. Then we want to be using the open source fork of mapbox, so follow the instructions here: https://visgl.github.io/react-map-gl/docs/get-started/get-started and install:

    npm i maplibre-gl
    

    Note that "i" is just short for install, and if you ever used --save or -S in the past, you don't need that any longer since it's automatic. You do still need --save-dev or -D for dev dependencies. For maplibre, we mainly need to follow these instructions:

https://visgl.github.io/react-map-gl/docs/get-started/get-started#using-with-a-mapbox-gl-fork and https://docs.maptiler.com/maplibre-gl-js/get-started/ https://www.maptiler.com/maps/#darkmatter//vector/1/0/0

but unfortunately they aren't complete. The css import from mapbox has been renamed, so we have to use the new name not the old one, because the filename itself has changed. And because we need to alias the package name...

  1. The very next step is add a one liner to your webpack config. Unfortunately, unless you want to add craco (which is totally fine - (https://github.com/gsoft-inc/craco)), there's no way to configure apps created by created-react-app without "ejecting". Ejecting just lifts the covers from your eyes, and all the complexity of properly building, bundling, testing, etc is revealed from create-react-app. [HERE SHOW PICTURE - https://twitter.com/chrismarx/status/1442871753466859520/photo/1]. Ejecting isn't so bad though, especially for a smaller project that you might not ever really need to upgrade to all the latest and greatest features and depenendencies automatically. Plus, we will building most of this app not using create-react-app, so the code within this project will be minimal.

    git add .
    git commit -m "initial commit"
    npm run eject
    
  2. Once ejected, there is also an issue with mapbox/maplibre and transpilation which they note here:

https://docs.mapbox.com/mapbox-gl-js/guides/migrate-to-v2/#transpiling or https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-react/#add-mapbox-gl-js

Annoyingly, those exact instructions didn't work for me. When using react, angular, vue, the clis are also using webpack under the hood, and one thing webpack does is transpilation (es6 - es5). However, even leaving our app at ES6 with browserlist isn't enough for this esoteric issue with mapbox and webworkers. This is what worked for me:
```
loader: require.resolve("babel-loader"),
...
ignore: ["./node_modules"],
```
  1. Copy the starter map code from react-map-gl, the first thing we'll need to do is add some typings. There's no great way to find the ViewportProps, normally we should have been able to 12 from the "onViewportChange" prop, and find the type of function and arguments its expecting. But with typescript, I feel like I'm not coding in the dark, I can instantly jump to the relevant source code, instead of reading over documentation sites. Make sure to add the stylesheet.

  2. And bonus, the first thing we see is that the compass control icon is missing. If you want a more seamless development experience, you should probably stick with mapbox proper, but we're at FOSS4G, so we are going to trudge forward and find the fix, and while we're at it, we can comment on the issue in the repo, with a codesandbox example and explanation for a fix. If we were really adventurous, we could make a PR, but this is probably enough for now, lol:

    https://github.com/visgl/react-map-gl/issues/1467
    
  3. Then we'll add some layers (REMEMBER DIRECTLY IN THE BackdropMap component for the moment!!!) so we have something to look at, deck makes this really easy. Here I'm just visualizing some of the other sample datasets they have in their repo, but you can see the format for the data on the deck documentation pages for the different layer types.

    npm install deck.gl
    npm i -D @danmarshall/deckgl-typings
    #AND add the deckgl.d.ts typings to the /src folder
    
  4. As our first foray into components, we could customize the navigation control, and add a custom buttom for resetting the viewport. At this point, to really make have our micro-frontend component driven design shine, we're actually going to create a whole different project, that will serve purely as a library for our components.

    npm i rmg-component-lib
    
  5. You said Typescript though, why. I recommend programming in Typescript because it's like having a friend help you code, it's like not coding in the dark. Typescript catches your errors as you're making them, rather than once you've actually run your program, and given that in large application, plenty of the app doesn't all run at startup, it also finds the errors that you won't encounter until you visit that particular section of your application. It also gives you a lot of visibility into what is actually happening when you're passing in or returning values, and easily links you to documentation. I'll show examples of all of these as we go through the creation of our app today. Lastly, I'd say that Typescript attempts to only demand the smallest modicum of additional code, and does as much as possible to not have you spending time on types. It will however, force you to actually understand the basics of the language, because the errors will be incomprehensible if you don't understand arrays, objects, etc.

Create a simple component

  1. To get started, lets create a super simple component. We can easily add a full screen control to our app because react-map-gl already made one for us, but how about a home button? Something that will reset the zoom/location for us. We can do that quite easily.
  2. We haven't seen yet much benefit for this component based approach yet, the map component is basically the whole page, and to use it we kinda need to interact with it in the browser environment. But for this component, we can actually build it in isolation, without waiting for map refreshes.
  3. Add storybook support
    npx -p @storybook/cli sb init
    
  4. Build out the home component, start the storybook script, show the components in isolation. TODO show the actions working
  5. The important things to note here is that the component is essentially a pure function, one in which the results or output is reliably defined by its inputs. The output is also lifted up out of the component, so that it can be orchestrated by the parent component.

Creating a side panel component with data layers

  1. Ok, let's make this map more interesting.

    npm install deck.gl
    

    Deck.gl is an extension for visualizations using mapbox, also open source from Uber. However, the first we find when trying to use this in our project is that vscode doesn't seem to recognize the library at all. That's because the project hasn't officially released it's typings yet. When developing, you'll typically find many projects that were already written in typescript, so the typings are there already, the typings are contained in the package, so you don't need to do anything extra, or you'll get a message like this:

    Could not find a declaration file for module 'my-untyped-module'. '.../node_modules/my-untyped-module/index.js' implicitly has an 'any' type.
    Try `npm install @types/my-untyped-module` if it exists or add a new declaration (.d.ts) file containing `declare module 'isomath';`  TS7016
    

    Often, the types are usually available from the @types package repo, which can contain user-submitted types for libraries as well. In thi case, a quick google search reveals they haven't even made it there yet, but they do exist:

    https://github.com/danmarshall/deckgl-typings

Moving components to a reusable library

  1. Everything we've done up until now is just fine for this project, but if you wanted to use any of the components we've created in another react app, you'd have to copy and paste those files into the new project. That defeats a lot of the point for component driven design. If you're building a lot of maps, you want one set of reusable peices, and if you update them, you at least want the option to update multile projects as well.

  2. Unfortunately, out of the big three (angular, react, vue), react is the only project without a standardized cli that is capable of scaffolding a new react library project. Which is really disappointing, and I think we should all be making noise about this. So, this will involve a bit more manual setup that our initial project, but it's still worth it.

  3. Steps

    • mkdir rmg-component-lib && cd rmg-component-lib
    • npm init
    • npm i -D typescript
    • touch tsconfig.json - and add tsconfig contents
    • Add peer dependencies
    • npm i -D react-dom react @types/react-dom @types/react
    • npm link ../deck-layers-map/node_modules/react
  4. Now let's add material ui, since it's an easy place to start for building apps

    • npm i -D @mui/material @emotion/react @emotion/styled
    • npm install -D @mui/icons-material
    • We can look for a component at material-ui, like the drawer, and first just open the codesandbox and see if we can easily adapt it for our needs, and it looks like it can. We can move the button to the main section, get rid of the top header, and we can customize the component to accept the content for the "Main" area through transclusion (angular word, but basically using composition of components).
    • Then we can add it to our project, and it basically works, remove some padding, add some styles, switch to a floating action button, etc. We also need to improve our build scripts, because now we want tsc and copyfiles running at the same time, so add nodemon and exec:
    • npm install --save-dev nodemon
    • npm install --save-dev copyfiles
    • Then add build scripts and combine them to run whenever nodemon detects changes
  5. For our layer panel, we can use the example here to power a basic form selection:

  6. We can then publish the package to NPM:

    npm login
    npm publish
    #In other projects you'd
    npm i rmg-component-lib
    npm i #All the peer dependencies
    
  7. Have a quick look at storybook as well

    npm run storybook
    

Web Components

  1. Have you ever been working on a webpage, adding <img> or <video> tags, and thinking, why can't a just a <mapbox-map> tag? It would "just work", be configurable with attributes just like <img src="">, you wouldn't need to know any javascript, it would accept styles, classes, etc. Well, that's what web-components are. And it's also what Angular, React and Vue have been emulating for all these years. And now, finally, we're starting to see libraries of these components, so you don't need to be a JS wizard to add a map to a webpage:

https://www.webcomponents.org/

That's exactly what we can start doing:

https://www.webcomponents.org/element/PolymerVis/mapbox-gl

(and I would show this working, but it needs yarn and bower away, I ran out of time). We have this basic LayersDrawer now, but what if we want other people to use it? Or use it another app that's not using React? Now we can.

  1. The bad news - The bad news is that React has the least built-in support for publishing web-components. And I actually ran into some issues compiling css styles from the material-ui library and have it work with the shadowdom. If you feel moved, you should definitely voice your support for better web-component integration here:

https://github.com/facebook/create-react-app

In the meantime, we have to do a little manual work to mount our React component in a basic web component template.

  1. It should be as easy as this - https://github.com/bitovi/react-to-webcomponent. - But we're not there yet :( I would check out Lit Elements if you're interested in web-components, probably one of the more popular libraries that abstracts away some of the work - https://lit.dev/ But in general, this is built in native browser technology, so we can just head over to our trusty documentation from Mozilla: https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements

Or Google also has a decent guide: https://developers.google.com/web/fundamentals/web-components/customelements

  1. Steps:

    • We could read through all the Mozilla docs, or we could start with an example to copy
    • https://gilfink.medium.com/wrapping-react-components-inside-custom-elements-97431d1155bd
    • If we go through this article, we can see it's not much to add or learn, basically just mounting our component into an HTML element we create, and hooking up communication from the web-components parameters to the react components "props"
    • Note that we're not even going to use typescript for this example, but converting the TS to JS is as simple as could be, just remove some of the typings.
  2. Testing the web-component in plain js app with Parcel

    • https://github.com/h5bp/create-html5-boilerplate
    • npx create-html5-boilerplate new-site
    • npm i rmg-component-lib
    • #Note that because we didn't do any fancy bundling, we still need to install the dev dependencies as well
    • #Add our component definition to main.js, add the element to the index.html page
    • npm start
  3. Run through the file, how react is mounted when adding component, or when attributes change. Finally, we register the component using just regular javascript. Now we can add layer-drawer to our index.html page, it will output our component. Since the contents of the component are defined only by the props, anyone could come along and use this with their own layers, using the eventing system to listen for layer changes, and not have to learn about react at all. The layer-drawer is also implemented with a slot, so we can do the normal composition of components that we're used to in html.

Typescript

  1. We're going to see where typescript helps us all over the app, but an easy place to show it would be added props to a component, but failing to supply the React.FC definition, since otherwise props.* is an error, because we don't actually know what we're getting

1.Here's another good one, using useState but forgot to destructure? That'll throw a nice error to remind you of what you did wrong.

Misc

  1. Get started on codesandbox.io. Along with stackblitz and a few others, these online IDEs are really useful and learning to use them is a good idea, because if you ever run into problems with your app, your chances are a 1000x better that someone will actually help or diagnose the problem for you on stackoverflow or even in github (if it's really a bug in the js libraries). They remove the issue of "it only does/doesn't work on my machine". Everyone can take a look at the project we build, and they don't even have to check it out of github and do anything locally.