Paul
Paul

Reputation: 4436

React js delete point from the path update the Polygon

enter image description here

I have the following map, as you should on the map I have a polygon and markers which are the main points of the polygon, below I have the main points of the polygon printed, i.e. the markers.

I give the user the possibility to delete points of the polygon, as you can see below in the image by clicking on the X, the point is eliminated.

The problem is this, when I click on the X, the point is eliminated as a marker, but it seems to remain as a fixed point of the polygon, which it really shouldn't do, the polygon should change its shape, based on the eliminated point.

I am not able to understand where I am wrong. Can you give me some help.

Link: codesandbox

Index:

import React from "react";
import ReactDOM from "react-dom";

import Map from "./Map";
import "./styles.css";

//import { makeStyles } from "@material-ui/core/styles";
import ExpansionPanel from "@material-ui/core/ExpansionPanel";
import ExpansionPanelSummary from "@material-ui/core/ExpansionPanelSummary";
import ExpansionPanelDetails from "@material-ui/core/ExpansionPanelDetails";
import Typography from "@material-ui/core/Typography";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import ClearIcon from "@material-ui/icons/Clear";
import TextField from "@material-ui/core/TextField";

const API_KEY = "MY_API_KEY";

/*const useStyles = makeStyles(theme => ({
  root: {
    width: "100%"
  },
  heading: {
    fontSize: theme.typography.pxToRem(15),
    fontWeight: theme.typography.fontWeightRegular
  }
}));*/

const useStyles = theme => ({
  root: {
    width: "100%"
  },
  heading: {
    fontSize: theme.typography.pxToRem(15),
    fontWeight: theme.typography.fontWeightRegular
  }
});

const center = {
  lat: 38.9065495,
  lng: -77.0518192
};

//const classes = useStyles();
//className={classes.heading}

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      paths: [
        { lat: 38.97330905858943, lng: -77.10469090410157 },
        { lat: 38.9209748864926, lng: -76.9083102888672 },
        { lat: 38.82689001319151, lng: -76.92204319902345 },
        { lat: 38.82261046915962, lng: -77.0181735701172 },
        { lat: 38.90174038629909, lng: -77.14314305253907 }
      ]
    };
  }

  render() {
    const { paths } = this.state;

    return (
      <div className="App2">
        <Map
          apiKey={API_KEY}
          center={center}
          paths={paths}
          point={paths => this.setState({ paths })}
        />
        {paths.map((pos, key) => {
          return (
            <ExpansionPanel key={key}>
              <ExpansionPanelSummary
                expandIcon={<ExpandMoreIcon />}
                aria-controls="panel1a-content"
                id="panel1a-header"
              >
                <ClearIcon
                  style={{ color: "#dc004e" }}
                  onClick={() => {
                    paths.splice(key, 1);
                    console.log(paths);
                    this.setState({ paths: paths });
                  }}
                />
                <Typography>Point #{key}</Typography>
              </ExpansionPanelSummary>
              <ExpansionPanelDetails>
                <TextField
                  fullWidth
                  key={"lat" + key}
                  label="Latitude"
                  type="text"
                  value={pos.lat}
                  disabled={true}
                />
                <TextField
                  fullWidth
                  key={"lng" + key}
                  label="Longitude"
                  type="text"
                  value={pos.lng}
                  disabled={true}
                />
              </ExpansionPanelDetails>
            </ExpansionPanel>
          );
        })}
      </div>
    );
  }
}

//export default withStyles(useStyles)(App);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Map:

import React, { useState, useRef, useCallback } from "react";
import {
  LoadScript,
  GoogleMap,
  DrawingManager,
  Polygon,
  Marker
} from "@react-google-maps/api";

import "./styles.css";
const libraries = ["drawing"];

const options = {
  drawingControl: true,
  drawingControlOptions: {
    drawingModes: ["polygon"]
  },
  polygonOptions: {
    fillColor: `#2196F3`,
    strokeColor: `#2196F3`,
    fillOpacity: 0.5,
    strokeWeight: 2,
    clickable: true,
    editable: true,
    draggable: true,
    zIndex: 1
  }
};

class LoadScriptOnlyIfNeeded extends LoadScript {
  componentDidMount() {
    const cleaningUp = true;
    const isBrowser = typeof document !== "undefined"; // require('@react-google-maps/api/src/utils/isbrowser')
    const isAlreadyLoaded =
      window.google &&
      window.google.maps &&
      document.querySelector("body.first-hit-completed"); // AJAX page loading system is adding this class the first time the app is loaded
    if (!isAlreadyLoaded && isBrowser) {
      // @ts-ignore
      if (window.google && !cleaningUp) {
        console.error("google api is already presented");
        return;
      }

      this.isCleaningUp().then(this.injectScript);
    }

    if (isAlreadyLoaded) {
      this.setState({ loaded: true });
    }
  }
}

export default function Map({ apiKey, center, paths = [], point }) {
  const [path, setPath] = useState(paths);
  const [state, setState] = useState({
    drawingMode: "polygon"
  });

  const noDraw = () => {
    setState(function set(prevState) {
      return Object.assign({}, prevState, {
        drawingMode: "maker"
      });
    });
  };

  const onPolygonComplete = React.useCallback(
    function onPolygonComplete(poly) {
      const polyArray = poly.getPath().getArray();
      let paths = [];
      polyArray.forEach(function(path) {
        paths.push({ lat: path.lat(), lng: path.lng() });
      });
      setPath(paths);
      console.log("onPolygonComplete", paths);
      point(paths);
      noDraw();
      poly.setMap(null);
    },
    [point]
  );

  /*const onLoad = React.useCallback(function onLoad(map) {
    //console.log(map);
  }, []);

  const onDrawingManagerLoad = React.useCallback(function onDrawingManagerLoad(
    drawingManager
  ) {
    // console.log(drawingManager);
  },
  []);*/

  // Define refs for Polygon instance and listeners
  const polygonRef = useRef(null);
  const listenersRef = useRef([]);

  // Call setPath with new edited path
  const onEdit = useCallback(() => {
    if (polygonRef.current) {
      const nextPath = polygonRef.current
        .getPath()
        .getArray()
        .map(latLng => {
          return { lat: latLng.lat(), lng: latLng.lng() };
        });
      setPath(nextPath);
      point(nextPath);
    }
  }, [setPath, point]);

  // Bind refs to current Polygon and listeners
  const onLoad = useCallback(
    polygon => {
      polygonRef.current = polygon;
      const path = polygon.getPath();
      listenersRef.current.push(
        path.addListener("set_at", onEdit),
        path.addListener("insert_at", onEdit),
        path.addListener("remove_at", onEdit)
      );
    },
    [onEdit]
  );

  // Clean up refs
  const onUnmount = useCallback(() => {
    listenersRef.current.forEach(lis => lis.remove());
    polygonRef.current = null;
  }, []);

  console.log(path);

  return (
    <div className="App">
      <LoadScriptOnlyIfNeeded
        id="script-loader"
        googleMapsApiKey={apiKey}
        libraries={libraries}
        language="it"
        region="us"
      >
        <GoogleMap
          mapContainerClassName="App-map"
          center={center}
          zoom={10}
          version="weekly"
          //onLoad={onLoad}
        >
          {path.length === 0 ? (
            <DrawingManager
              drawingMode={state.drawingMode}
              options={options}
              onPolygonComplete={onPolygonComplete}
              //onLoad={onDrawingManagerLoad}
              editable
              draggable
              // Event used when manipulating and adding points
              onMouseUp={onEdit}
              // Event used when dragging the whole Polygon
              onDragEnd={onEdit}
            />
          ) : (
            <Polygon
              options={{
                fillColor: `#2196F3`,
                strokeColor: `#2196F3`,
                fillOpacity: 0.5,
                strokeWeight: 2
              }}
              // Make the Polygon editable / draggable
              editable
              draggable
              path={path}
              // Event used when manipulating and adding points
              onMouseUp={onEdit}
              // Event used when dragging the whole Polygon
              onDragEnd={onEdit}
              onLoad={onLoad}
              onUnmount={onUnmount}
            />
          )}
          {path.map((pos, key) => {
            return <Marker key={key} label={"" + key} position={pos} />;
          })}
        </GoogleMap>
      </LoadScriptOnlyIfNeeded>
    </div>
  );
}

Upvotes: 4

Views: 2019

Answers (2)

Alex
Alex

Reputation: 3991

Add useEffect to Map component to update paths for each render

export default function Map({ apiKey, center, paths = [], point }) {
  const [path, setPath] = useState();
  const [state, setState] = useState({
    drawingMode: "polygon"
  });
  useEffect(() => {
    setPath(paths);
  }, [paths]);
 .
 .
 .

and use filter instead of splice

In React you should never mutate the state directly.

 onClick={() => {
                    this.setState({
                      paths: this.state.paths.filter((_, i) => i !== key)
                    });

or use splice like below

 onClick={() => {
                   
                    const paths = this.state.paths;
                    this.setState({ 
                      paths: [...paths.slice(0,key), ...paths.slice(key+1)]})

                  }}

codesandbox

Upvotes: 2

Raghul SK
Raghul SK

Reputation: 1390

During the Cancel Event, Call the function with the index of that array in the function parameter .

removetheArray = value => {
    const { paths } = this.state;
    this.setState({
      paths: paths.splice(value, 1)
    });
  };

Function name is the removetheArray and pass the index as the value, the array as removed and map is updated incase map is not updated you have to init the map.

Upvotes: 1

Related Questions