3
votes

How can I create elements outside of a Mapbox map object that can be dragged into it? For example, let's say I want to render a list of locations on a page. Each location is a React component with a custom marker or icon.

Next to this list of locations is a Mapbox map. The list of locations is not rendered inside the map. While I know it's possible to make these individual location components draggable, is it possible to drag and drop them into a Mapbox map and have it recognized as actual markers with latitude/longitude coordinates on the map? If so, how can I do this?

Here are the relevant source files in my code that I have tried:

index.js

import dynamic from "next/dynamic";
import { useSelector } from "react-redux";
import Plant from "../../components/Plant";

const MapboxMap = dynamic(() => import("../../components/MapboxGLMap"), {
  ssr: false,
});

const Blueprint = () => {
  const plants = useSelector((state) => state.plants);

  const showPlants = () => {
    return (
      <React.Fragment>
        {plants.map((plant) => (
          <Plant plant={plant} />
        ))}
      </React.Fragment>
    );
  };

  return (
    <React.Fragment>
      <div className="ui container centered grid blueprint">
        <div className="three wide column scrollable">
          <div className="ui link one cards">{showPlants()}</div>
        </div>
        <div className="twelve wide column">
          <MapboxMap />
        </div>
      <style jsx>{`
        .scrollable {
          height: calc(100vh);
          overflow-x: auto;
        }
      `}</style>
    </React.Fragment>
  );
};

export default Blueprint;

Plant.jsx

import React from "react";
import { useDrag } from "react-dnd";

const ItemTypes = {
  PLANT: "plant",
};

const Plant = ({ plant }) => {
  const [{ isDragging }, drag] = useDrag({
    item: { type: ItemTypes.PLANT },
    collect: (monitor) => ({
      isDragging: !!monitor.isDragging(),
    }),
  });
  return (
    <div
      ref={drag}
      style={{
        opacity: isDragging ? 0.1 : 1,
        cursor: "move",
      }}
      key={plant.id}
      className="card"
    >
      <div className="image">
        <img src="/white-image.png" />
      </div>
      <div className="content">
        <div className="center aligned">{plant.common_name}</div>
      </div>
    </div>
  );
};

export default Plant;

MapboxGLMap.jsx

import React, { useEffect, useRef, useState } from "react";
import mapboxgl from "mapbox-gl";
import MapboxGeocoder from "@mapbox/mapbox-gl-geocoder";
import MapboxDraw from "@mapbox/mapbox-gl-draw";

const MAPBOX_TOKEN = "xxx";

const styles = {
  width: "100%",
  height: "100%",
  position: "absolute",
};

const MapboxGLMap = () => {
  const [map, setMap] = useState(null);
  const [lng, setLng] = useState(null);
  const [lat, setLat] = useState(null);
  const [plant, setPlant] = useState(null);

  const mapContainer = useRef(null);

  useEffect(() => {
    mapboxgl.accessToken = MAPBOX_TOKEN;
    const initializeMap = ({ setMap, mapContainer }) => {
      const map = new mapboxgl.Map({
        container: mapContainer.current,
        style: "mapbox://styles/mapbox/satellite-v9", // stylesheet location
        center: [0, 0],
        zoom: 5,
      });

      map.on("load", () => {
        setMap(map);
        map.resize();
      });

      map.on("click", (e) => {});

      map.addControl(
        new MapboxGeocoder({
          accessToken: MAPBOX_TOKEN,
          mapboxgl: mapboxgl,
        })
      );

      const draw = new MapboxDraw({
        displayControlsDefault: false,
        controls: {
          polygon: true,
          trash: true,
        },
      });
      map.addControl(draw);

      map.on("draw.create", (e) => {
        console.log("e =>", e);
        console.log("draw.getAll()", draw.getAll());
      });

      map.on("mousemove", (e) => {
        // console.log(e.point);
        setLng(e.lngLat.wrap().lng);
        setLat(e.lngLat.wrap().lat);
      });
    };

    if (!map) initializeMap({ setMap, mapContainer });
  }, [map]);

  return <div ref={(el) => (mapContainer.current = el)} style={styles} />;
};

export default MapboxGLMap;
1
You will have to post the code you have tried form your endmishsx
Hey. How did you manage to get the react-dnd working with Mapbox? I can see you have useDrag on your component, but how do you manage the drop action on the map itself?szczocik

1 Answers

6
votes

Actually, based on your related tags I imagine that you wanna drag and drop something like pin from outside to the map area. you use reactjs tags, it means you wanna do it by using ReactJS.

For this, you should install Mapbox by npm or yarn:

npm install mapbox-gl --save

or

yarn add mapbox-gl

Then you should wrap the Mapbox area in a drop zone. for this, you could use react-dropzone. install it by the following command:

npm install react-dropzone --save

or

yarn add react-dropzone

Add the following line to HTML template:

<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.11.1/mapbox-gl.css' rel='stylesheet' />

Then use it like below:

import React from 'react';
import Dropzone from 'react-dropzone';
import mapboxgl from 'mapbox-gl';

mapboxgl.accessToken = 'MAPBOX_ACCESS_TOKEN';

class MapComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      lng: 5,
      lat: 34,
      zoom: 2,
    };
  }

  componentDidMount() {
    const map = new mapboxgl.Map({
      container: this.mapContainer,
      style: 'mapbox://styles/mapbox/streets-v11',
      center: [this.state.lng, this.state.lat],
      zoom: this.state.zoom,
    });

    map.on('move', () => {
      this.setState({
        lng: map.getCenter().lng.toFixed(4),
        lat: map.getCenter().lat.toFixed(4),
        zoom: map.getZoom().toFixed(2),
      });
    });
  }

  render() {
    return (
      <Dropzone>
        {({ getRootProps, getInputProps }) => (
          <section>
            <div {...getRootProps()}>
              <input {...getInputProps()} />
              <div
                ref={el => {
                  this.mapContainer = el;
                }}
              />
            </div>
          </section>
        )}
      </Dropzone>
    );
  }
}

By using this method you can drop some images and got it and based on the drop position show it on the map.

Note: reduce the file type catching to image file type like jpg/png