Web applications have greatly benefited from the inclusion of maps, providing users with valuable location-based information. Maps have transformed our interaction with the world, from navigating unfamiliar places to discovering nearby eateries. As a result, the integration of maps into websites has become increasingly popular in recent times. Nevertheless, designing maps that are both functional and user-friendly can present a challenge, especially for those lacking experience in this domain. In this article, I will share useful tips on how to create effective maps within your browser. Tech stack Let’s discuss technologies. When working with maps, we typically use three layers: Rendering the user interface, which includes buttons and forms. In our stack, React performs this role; Rendering the map and enabling user interaction. We use Mapbox for this; Fetching data from the server, such as information about markers or polygons. To retrieve data, we use the browser’s built-in fetch feature. Let’s review each item to gain a better understanding of our technology stack when working with maps. React The library enables you to work with page elements conveniently and efficiently. It was developed by Facebook for their own use, and includes numerous components such as buttons, forms, and other interactive elements on the pages. For example, Facebook created a to quickly compare different states. React reconciliation algorithm As changing elements on a page is the most expensive operation for a browser, it’s essential to do it as efficiently as possible. To address this issue, Facebook engineers developed the React library, which enables fast and straightforward element changes on a page. Besides providing rapid state changes on a page, React allows us to do this declaratively without working with DOM elements directly. Instead, we use an abstraction, usually , which looks like HTML. Let’s consider an example: JSX // It's our state. Is the user our friend or not? // false by default const [isFriend, setIsFriend] = useState(false) // Depending on the state, we show the text on the button const buttonText = isFriend ? 'Your my Friend' : 'Add as Friend' // There is JSX, syntax for UI // In this case, we display a button, when clicked, we change the state return ( <button onClick={() => setIsFriend(true)}>{buttonText}</button> ) It is possible to nest components with ordinary DOM elements like forms, buttons, and inputs at the bottom of the hierarchy. By assembling these simple elements, we can create more complex ones, such as a complete form: const Form = () => ( <form> <input name="Email"/> <input name="Password"/> </form> ) const App = () => ( <main> <h1>My form!</h1> <Form /> </main> ) How does React assist us in the context of maps? As the map on the page is interactive, similar to a button or form, we aim to optimize its rendering and interaction through events like clicks on the map. React can help achieve this optimization. Here is an example of how it works: // Use React to render the map with different event handlers // and render markers return ( <BaseMap onInitMap={() => console.log('I am alive!')} onClickMap={() => console.log('Click!')} onDestroyMap={() => console.log('Oh no!')} > <ClustersMarkers /> <PostsMarkers /> <ListingsMarkers /> </BaseMap> ) When working with React, it is essential to remember that it allows for efficient manipulation of elements on the page, quick changes, and interaction with them through events. This is achieved through an abstraction that resembles HTML, making it easy to create complex components from simpler ones. Mapbox Now, let’s discuss the map itself. Creating and using maps can be challenging, and only a few product companies can design maps from scratch. Typically, most people rely on pre-made libraries with a user-friendly API that has been tried and tested. Numerous dynamic map providers are available, including Google Maps, Leaflet, Bing Maps, Mapbox, and more. However, we will focus on due to its extensive features and fair pricing policy. For instance, Google Maps charges for 100k map views per month, while Mapbox only charges . Moreover, Mapbox offers free access for up to 50k map views per month. Mapbox $700 $250 Mapbox offers , a tool often compared to Photoshop for maps. With this tool, designers can create custom styles, hide unnecessary map elements, and remove house displays. One of its benefits is the ability to enhance the visibility of . Another advantage is the opportunity to experiment with map styling in corporate colors. However, it’s essential to consider the user experience and maintain a familiar color palette, including green grass and blue water. The best part about Mapbox Studio is that it simplifies the map design process by eliminating the need for developing and transferring requirements, ultimately reducing the cost of working with maps. Mapbox Studio Points of Interest Additionally, Mapbox provides a that can convert addresses to coordinates or vice versa, simplifying the process of locating points on a map. However, the tool’s may be insufficient, leading to difficulties for certain projects. Mapbox gathers data from , such as governments, open data initiatives like OpenStreetMap, and private companies. geocoding tool worldwide coverage numerous sources Mapbox provides a variety of , although the could benefit from some improvement. For instance, when incorporating marker functionality, examples can be more informative than the documentation. Additionally, challenges may arise when merging multiple components, such as marker clusters, custom data loading, normalization, and marker state changes. practical examples documentation Let’s revisit Mapbox maps. What does the do? Mapbox API It initializes the map in an HTML element on the page; It loads and renders images that make up the map; It draws additional elements, such as markers, using GeoJson as input data; It generates events, such as clicks or zoom changes, that can be handled. Let’s take a closer look at each of these items. Mapbox specializes in map rendering using tiles. Tiles are small square images that make up the larger map. The default size of a tile is 512x512 pixels, and it can be either . tiles are used to display roads, buildings, Points of Interest (PoI), and more. They can be dynamically styled, are lightweight, and allow for smooth interaction with the map. tiles, on the other hand, are used for displaying satellite imagery. vector or raster Vector Raster Just so you know, Mapbox Studio allows us to choose the specific data we want to include in the map tiles. These tiles are then placed onto a , which is a special DOM element in the browser that displays images and other graphical elements on the web. To give you an idea, it’s similar to how documents are displayed on a canvas in Google Docs. canvas <canvas width="100" height="100" /> Mapbox handles the loading, insertion, and updating of tiles. All we have to do is specify where we want the map to be displayed and the initial conditions, like the zoom level or the map’s coordinates. To use Mapbox, you’ll need an , which is a unique key that can be found in your Mapbox account. To set up a basic map, here’s a quick example, but for more information, check out the provided: access token link mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN'; const map = new mapboxgl.Map({ container: 'map', // we can use an Id or an element style: 'mapbox://styles/mapbox/streets-v11', // URL for styles center: [-74.5, 40], // initial coordinates [lng, lat] zoom: 9, // initial zoom }); After this, we will get a map on the page in an element with the id ‘map.’ To provide users with more information on the map, we often display the location of a certain establishment or the boundaries of a specific area. To achieve this, we use a specific data format called and instruct Mapbox on how to showcase this data. GeoJSON GeoJSON is the standard format for storing geographic structures on maps. It can store various primitive types that describe geographic objects such as addresses, locations, streets, highways, borders, countries, states, and combinations of these, known as multipart. GeoJSON was introduced in 2008 and is represented like this: { "type": "Feature", // also can be FeatureCollection, it's collection of Feature "geometry": { "type": "Point", // also can be LineString, Polygon, MultiPolygon "coordinates": [125.6, 10.1] // for other types you can use Array with coordinates }, "properties": { // it's metadata, we can you that to show something on the map "name": "Dinagat Islands" } } Let’s talk about the coordinate system used in Mapbox. By default, Mapbox employs , which is a way of projecting the Earth onto a flat surface to work with tiles. This is known as Web Mercator and is the standard for online maps. However, when dealing with data about markers and polygons, it uses a different coordinate system called . This system relies on latitude and longitude to describe coordinates on the Earth’s ellipsoid. Mapbox and other map providers automatically convert coordinates from EPSG 4326 to EPSG 3857. If we need to work with different , we can use the method. EPSG 3857 EPSG 4326 projections map.setProjection Now, we will discuss how to display GeoJSON on the map. Mapbox offers two entities that we will find helpful: — This is the data seed. We can create a new source with GeoJSON data and configure it, for example, to generate an ID for each feature in the feature collection. Source — This is the data representation. We can display the data from the source in different ways, such as showing boundaries. Layer To display polygons or markers on the map, we must retrieve the data in GeoJson format from the server. Then, we create a source, input the data into it, and connect it to the required layer. const geoJsonFeature = { 'type': 'Feature', 'geometry': { 'type': 'Polygon', 'coordinates': [ [-67.13734, 45.13745], [-66.96466, 44.8097], [-68.03252, 44.3252], [-67.13734, 45.13745] ] } } // Create source with our data map.addSource('ourSource', { 'type': 'geojson', 'data': geoJsonFeature }); // Add layer for background map.addLayer({ 'id': 'background', 'type': 'fill', 'source': 'ourSource', // название нашего source 'layout': {}, 'paint': { 'fill-color': '#0080ff', 'fill-opacity': 0.5 } }); // Add layer for border map.addLayer({ 'id': 'border', 'type': 'line', 'source': 'ourSource', 'layout': {}, 'paint': { 'line-color': '#000', 'line-width': 3 } }); After running this code, we get the result: To learn more about this topic, you can refer to the provided by Mapbox. In this tutorial, we have covered the process of initializing the map and presenting our data. Next, we will explore how to handle events like clicking, dragging, and zooming. For instance, we can use Mapbox to monitor events and display the coordinates in the console as the user moves their cursor on the map. To achieve this, we simply call the method with the event type we want, similar to how we work with DOM elements. documentation on map.on('mousemove', (e) => { console.log(JSON.stringify(e.point)); }); // Result: {"x":330,"y":49} In summary, what do we need to remember? Mapbox allows us to display a map, draw our data on top of it, and process map events. At the same time, Mapbox takes care of loading and displaying images (tiles). Fetch A word about . We have already seen how to render data on a map, but first, we must retrieve it from the server. When we dynamically request data from the server in the background without reloading the page, we call this approach (“ synchronous avaScript nd ML”). There are many tools for loading data asynchronously from the server, such as Axios or XMLHttpRequest (native). fetch AJAX A J a X What to remember? We retrieve data from the server, and there are many libraries for this, but we will use fetch. Next, we will look at how we do this specifically when working with maps, as there are nuances. React + Mapbox Now let’s see how the technologies described above work together. First, we will retrieve the data for displaying the polygon using fetch. Then we will declare the initialization of the map, and after it loads, we will add the polygon to the map. You can also find a working example at the provided. link const useFetch = () => { /* Our data { 'type': 'Feature', 'geometry': { 'type': 'Polygon', 'coordinates': [ [ [-67.13734, 45.13745], [-68.03252, 44.3252], [-68.90478, 47.18479], [-67.13734, 45.13745], ] ] } } */ const [data, setData] = useState(null) useEffect(() => { fetch('https://our-api.com/polygon') .then(response => response.json()) .then(setData) .catch(e => { console.error(e) }) }, [setData]) return { data } } const BaseMap = () => { // Use the hook to fetch data const { data } = useFetch(GET_REGION); // Map instance const map = useRef(null); // DOM element const mapContainer = useRef(null); // Main logic - init the map and add the event useEffect(() => { if (map.current) { return; // initialize map only once } mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN'; map.current = new mapboxgl.Map({ container: mapContainer.current, style: 'mapbox://styles/mapbox/light-v10', // style URL (it's Mapbox's core style) center: [-68.137343, 45.137451], // starting position zoom: 5 // starting zoom }); // Handle event map.on('load', () => { const sourceId = 'source-region' // Add a data source containing GeoJSON data map.addSource(sourceId, { 'type': 'geojson', 'data': data.region // our data from Apollo }); // Add a new layer to visualize the polygon map.addLayer({ 'id': 'background', 'type': 'fill', 'source': sourceId, // reference the data source 'paint': { 'fill-color': '#0080ff', // blue color fill 'fill-opacity': 0.5 } }); // Add a black outline around the polygon map.addLayer({ 'id': 'outline', 'type': 'line', 'source': sourceId, 'paint': { 'line-color': '#000', 'line-width': 3 } }); }); }); return <div ref={mapContainer} />; } Conclusion We looked at the technology stack that underpins our future architecture. In the following article, we will discuss the principles that help design a map architecture, how to achieve maximum low coupling and high cohesion of modules, and how to maintain and develop a scalable map system. Thank you very much for your attention! Have a great day.