Estoy creando una aplicación de folleto de reacción, y estoy tratando de separar el control del zoom del mapa en sí. Aquí se hizo la misma pregunta en un contexto de folleto de vainilla: Colocación de controles fuera del mapa contenedor con folleto?. Esto es lo que estoy tratando de lograr dentro del marco del folleto reaccionar. Aquí está el bosquejo general de mi proyecto:

import UIWindow from './UIWindow'
import Map from './MapRL'

class App extends React.Component {
   render () {
      return (
         <div className="App">
            <Map />
            <UIWindow />
         </div>
      )
   }
}

export default App;

Mi componente de mapa se ve así:

import React from 'react'
import { Map as LeafletMap, TileLayer } from 'react-leaflet'

class Map extends React.Component {

   render () {
      return(
         <LeafletMap
            className="sidebar-map"
            center={...}
            zoom={...}
            zoomControl={false}
            id="mapId" >

            <TileLayer
                url="..."
                attribution="...
            />

         </LeafletMap>
      )
   }
}

export default Map;

Entonces mi UIWindow se ve así:

class UIWindow extends React.Component {
   render () {
      return(
         <div className="UIWindow">
            <Header />
            <ControlLayer />
         </div>
      )
   }
}

Y finalmente, mi ControlLayer (donde quiero que viva mi ZoomControl) debería verse así:

class ControlLayer extends React.Component {
   render () {
      return (
         <div className="ControlLayer">
            <LeftSidebar />
            <ZoomControl />
            {this.props.infoPage && <InfoPage />}
         </div>
      )
   }
}

Por supuesto, con este código actual, poner ZoomControl en ControlLayer arroja un error: TypeError: No se puede leer la propiedad '_zoom' de undefined , con una descripción más detallada de lo que está mal, con todas las referencias internas del folleto código sobre la funcionalidad de zoom. (DomUtil.removeClass(this._zoomInButton, className);, etc.)

Esperaba un error, porque ZoomControl ya no es un hijo del componente <Map />, sino un nieto del hermano de <Map />. Conozco las funciones react-leaflet en su proveedor y consumidor de contexto, LeafletProvider y LeafletConsumer. Cuando intento llamar a mi LeafletConsumer desde mi <ControlLayer />, no recibo nada. Por ejemplo:

            <LeafletConsumer>
               {context => console.log(context)}
            </LeafletConsumer>

Esto registra un objeto vacío. Claramente, mi LeafletConsumer de mi ControlLayer no está correctamente conectado al LeaflerProvider desde mi <Map />. ¿Necesito exportar el contexto del Mapa de alguna manera usando LeafletProvider? Soy un poco nuevo en React Context API, por lo que esto aún no es intuitivo para mí. (La mayor parte del resto de la aplicación utilizará React Redux para administrar los cambios de estado y la comunicación entre los componentes. Así es como planeo conectar los contenidos de la barra lateral al mapa en sí. Mi barra lateral no parece tener ningún problema con estar totalmente desconectado de <Map />).

¿Cómo puedo conectar correctamente este ZoomControl a mi componente de Mapa?

ACTUALIZACIÓN:

Intenté capturar el contexto en mi tienda redux y luego servirlo a mi ZoomControl externo. Al igual que:

            <LeafletConsumer>
               { context => {
                  this.props.captureContext(context)
               }}
            </LeafletConsumer>

Esto captura el contexto como parte de mi tienda redux. Luego uso esto como un valor en un nuevo contexto:

// ControlLayer.js

const MapContext = React.createContext()

            <MapContext.Provider value={this.props.mapContext}>
               <LeftSidebar />
               <MapContext.Consumer>
                  {context => {
                     if (context){
                        console.log(ZoomControl);

                     }
                  }}
               </MapContext.Consumer>
            </MapContext.Provider>

Donde this.props.mapContext es traído de mi redux matchStateToProps, y es exactamente el contexto capturado por la función captureContext.

Aún así, esto no está funcionando. Mis herramientas de desarrollo de reacción muestran que MapContent.Consumer está dando exactamente los mismos valores que los inherentes '' del folleto de reacción da cuando ZoomControl está dentro del componente Map. Pero todavía recibo el mismo mensaje de error. Muy frustrado por aquí.

3
Seth Lutske 21 dic. 2019 a las 02:03

2 respuestas

La mejor respuesta

Aquí está el mismo enfoque sin ganchos:

El proveedor debería verse así:

class Provider extends Component {
  state = { map: null };

  setMap = map => {
    this.setState({ map });
  };

  render() {
    return (
      <Context.Provider value={{ map: this.state.map, setMap: this.setMap }}>
        {this.props.children}
      </Context.Provider>
    );
  }
}

El componente del prospecto será:

class Leaflet extends Component {
  mapRef = createRef(null);

  componentDidMount() {
    const map = this.mapRef.current.leafletElement;
    this.props.setMap(map);
  }

  render() {
    return (
      <Map
        style={{ width: "80vw", height: "60vh" }}
        ref={this.mapRef}
        center={[50.63, 13.047]}
        zoom={13}
        zoomControl={false}
        minZoom={3}
        maxZoom={18}
      >
        <TileLayer
          attribution='&amp;copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?"
        />
      </Map>
    );
  }
}

Y ahora para acceder a la función setMap al componenteDidMount, debe hacer lo siguiente:

export default props => (
  <Context.Consumer>
    {({ setMap }) => <Leaflet {...props} setMap={setMap} />}
  </Context.Consumer>
);

Para el resto, eche un vistazo aquí: Demo

1
kboul 16 ene. 2020 a las 21:24

No estoy seguro de cómo lograr eso usando su enfoque donde el contenedor de react-leaflet ZoomControl no es un elemento secundario del contenedor de mapas cuando intenta colocarlo fuera del contenedor de mapas.

Sin embargo, para un control pequeño como el ZoomControl, una solución fácil sería crear un componente Zoom personalizado, idéntico al original, construirlo fácilmente usando el estilo css nativo y después de acceder al elemento del mapa, invocar los métodos de acercamiento y alejamiento respectivamente .

En el siguiente ejemplo, uso react-context para guardar el elemento del mapa después de que se carga el mapa:

useEffect(() => {
    const map = mapRef.current.leafletElement;
    setMap(map);
  }, [mapRef, setMap]);

Y luego use la referencia del mapa para hacer que un componente Zoom personalizado sea idéntico al nativo (para css, vea la demostración):

const Zoom = () => {
  const { map } = useContext(Context);

  const zoomIn = e => {
    e.preventDefault();
    map.setZoom(map.getZoom() + 1);
  };
  const zoomOut = e => {
    e.preventDefault();
    map.setZoom(map.getZoom() - 1);
  };

  return (
    <div className="leaflet-bar">
      <a
        className="leaflet-control-zoom-in"
        href="/"
        title="Zoom in"
        role="button"
        aria-label="Zoom in"
        onClick={zoomIn}
      >
        +
      </a>
      <a
        className="leaflet-control-zoom-out"
        href="/"
        title="Zoom out"
        role="button"
        aria-label="Zoom out"
        onClick={zoomOut}
      >
        −
      </a>
    </div>
  );
};

Y luego colócalo donde quieras:

const App = () => {
  return (
    <Provider>
      <div style={{ display: "flex", flexDirection: "row" }}>
        <Leaflet />
        <Zoom />
      </div>
    </Provider>
  );
};

Demo

1
kboul 23 dic. 2019 a las 10:53