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

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

What we're going to build:f

  • Finished product. I'm using deck.gl here mainly because it's just so easy to add interesting map visualizations, especially once you're already using react-map-gl, the react component library from Uber for mapbox. The custom reusable components we're going to add here are the home button to refocus the map, and a layer drawer for show/hiding layers, adding layer legends, etc. Two things that I've added to more maps than I can count.
  • Note that we're using mapbox (technically maplibre), 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 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-

Setup

  1. Git clone the blank repo

  2. 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.

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

    cd foss4g-2021-react-mapbox
    #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 --template typescript
    
  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 with 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:

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. [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.

  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. 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
    
  2. 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.

  3. 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.

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
    • npm init
    • npm init
  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:

Ok, now make it lit - 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="">, 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 show the latter working in our playground app?). 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 in-built support for publishing web-components. If you feel moved, you should definitely voice your support for better web-component integration here:

===link===

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.

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.

Clone this wiki locally