简体   繁体   中英

react-leaflet v4 set and change marker on click

I can't set and change the marker on click.

I get the user's location from the browser and center the map with these coordinates. When I run console.log(coords) I can see the coordinates that the browser picked up, but when I go to set the marker using [formValues.coords[0], formValues.coords[1]] as LatLngExpression it doesn't work. When I click on the map it's not changing the coordinates either. I am doing something wrong, but I haven't figured out where yet.

Another thing I saw is that the whenCreated property has been removed from react-leaflet v4:

"Removed whenCreated property from the MapContainer component (a ref callback can be used instead)."

But I don't know how to use this ref callback in this context.

Can anyone help me? Here is the code in the map rendering part:

import {
  Button,
  ButtonContainer,
  CategoryBox,
  CategoryContainer,
  CategoryImage,
  Container,
  Form,
  FormTitle,
  MapContainer,
  Section,
} from "./styles";
import Input from "../../components/Input";
import { useState } from "react";
import { LatLngExpression, LeafletMouseEvent } from "leaflet";
import { TileLayer, Marker } from "react-leaflet";
import { categories } from "./categories";
import useGetLocation from "../../hooks/useGetLocation";
import { toast } from "react-toastify";
import { useNavigate } from "react-router-dom";

export default function New() {
  const navigate = useNavigate();
  const [formValues, setFormValues] = useState({
    name: "",
    description: "",
    contact: "",
    category: "",
    coords: [0, 0],
  });
  const { coords } = useGetLocation();
  console.log(coords);

  async function onSubmit() {
    const request = await fetch("http://localhost:5000/store", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        ...formValues,
        latitude: formValues.coords[0],
        longitude: formValues.coords[1],
      }),
    });

    if (request.ok) {
      toast("Estabelecimento gravado com sucesso!", {
        type: "success",
        autoClose: 2000,
        onClose: () => navigate("/"),
      });
    }
  }

  if (!coords) {
    return <h1>Obtendo localização...</h1>;
  }

  return (
    <Container>
      <Form
        onSubmit={(ev) => {
          ev.preventDefault();
          onSubmit();
        }}
      >
        <FormTitle>Cadastro do comércio local</FormTitle>
        <Section>Dados</Section>
        <Input
          label="Nome do local"
          name="name"
          value={formValues.name}
          onChange={setFormValues}
        />
        <Input
          label="Descrição"
          name="description"
          value={formValues.description}
          onChange={setFormValues}
        />
        <Input
          label="Contato"
          name="contact"
          value={formValues.contact}
          onChange={setFormValues}
        />
        <Section>Endereço</Section>
        <MapContainer
          center={
            {
              lat: coords[0],
              lng: coords[1],
            } as LatLngExpression
          }
          zoom={13}
          whenCreated={(map) => {
            map.addEventListener("click", (event: LeafletMouseEvent) => {
              setFormValues((prev) => ({
                ...prev,
                coords: [event.latlng.lat, event.latlng.lng],
              }));
            });
          }}
        >
          <TileLayer
            attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
            url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
          />
          <Marker
            position={
              [formValues.coords[0], formValues.coords[1]] as LatLngExpression
            }
          />
        </MapContainer>
        <Section>Categoria</Section>
        <CategoryContainer>
          {categories.map((category) => (
            <CategoryBox
              key={category.key}
              onClick={() => {
                setFormValues((prev) => ({ ...prev, category: category.key }));
              }}
              isActive={formValues.category === category.key}
            >
              <CategoryImage src={category.url} />
              {category.label}
            </CategoryBox>
          ))}
        </CategoryContainer>
        <ButtonContainer>
          <Button type="submit">Salvar</Button>
        </ButtonContainer>
      </Form>
    </Container>
  );
}

Yeah, so WhenCreated is just not a prop anymore. And I think there is an easier way that doesn't involve messing with ref s. First thing you need to do is move your map container so that it is a wrapper, then you can use something like this to make a marker move to wherever you click:

function MovingMarker() {
  const [position, setPosition] = useState(center)
  const markerRef = useRef(null)
  const map = useMapEvent('click', (e) => {
  console.log("click")
    setPosition(e.latlng)
  })
  
  return (
    <Marker
      position={position}
      ref={markerRef}>
    </Marker>
    
  )
}

render(

    <MapContainer center={center} zoom={13} scrollWheelZoom={false}>
        <TileLayer
          attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        <MovingMarker />

  
  </MapContainer>
)

You can run this demo on https://react-leaflet.js.org/docs/api-map/ if you want to play around with it in a live environment. If you need to pull the co-ordinates of the marker up and outside of the MapContainer then I'd advise adding a prop like onChange={(e)=>{setGlobalMarkerPos(e)}} to the MovingMarker component. That way any change will be propagated up the component stack. Or you could use a context manager.

An alternate solution would be to get a reference to the map:

function RealMapContainer() {
  const [position, setPosition] = useState(center)
  const [listenerSetup, setListenerSetup] = useState(null)
  const mapRef = useRef(null)
  if(mapRef.current && !listenerSetup){
    console.log(mapRef);
    mapRef.current.on('click', (e)=>{
      setPosition(e.latlng) 
    })
    setListenerSetup(true)
  }
  //if we still haven't setup the listener, 
  //then setListenerSetup(false) is just a way to make this
  //component keep rendering over and over until the reference 
  //is finally defined and the listener can be set up
  else if(!listenerSetup){
    setTimeout(()=>{ 
      setListenerSetup(false)
    }, 100)
  }
  
  return (
    <MapContainer center={center} zoom={13} scrollWheelZoom={false} ref={mapRef}>
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={position}/>
  
    </MapContainer>
    
  )
}

Then you would have an actual reference to the MapObject and use that in all the ways the vanilla leaflet documentation specifies.

I added a handler for clicks that moves a marker position. That whole thing with listenerSetup is just a stupid way to get the thing to keep re-rendering until it finds a reference to the map object. I almost exclusively use class-based React, so I'm sure there is a better way but that works. If anyone reading this knows a better way, please leave a comment and let me know so I can update.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM