0

i'm trying to bind react-bootstrap accordion to a leaflet.js marker. when clicking on accordion, marker will get zoomed-in... the problem is i have to "double click" on the accordion to collapse it and zoom-in the marker => because accordion is being refreshed when focusing on marker

**codesandbox demo **

https://codesandbox.io/s/react-leaflet-with-imageoverlay-add-remove-markers-dynamically-2tmex

Map Container

import React, { useEffect, useState } from "react";
import "leaflet/dist/leaflet.css";
import L from "leaflet";
import masterplan from "./img/masterplan.jpg";






let map;
const markerIcon = 'https://www.pinclipart.com/picdir/big/141-1410207_marker-filled-icon-location-logo-png-file-clipart.png'
const editMarkerIcon = 'https://cdn.iconscout.com/icon/premium/png-256-thumb/marker-1829264-1551716.png'
var myIcon = L.icon( {
  iconUrl: markerIcon,
  iconSize: [ 30, 35 ],
  iconAnchor: [ 15, 35 ],
  popupAnchor: [ 0, -40 ],
} );

var editMyIcon = L.icon( {
  iconUrl: editMarkerIcon,
  iconSize: [ 50, 40 ],
  iconAnchor: [ 25, 35 ],
  popupAnchor: [ 0, -40 ],


} );




const MapContainer = ( { data, linkName, formCss, addMarker, isMarkerAdded, setIsMarkerAdded, formData, setFormData, setGetMarkerObject, setMarkersList, setEdit, clicked, setClicked } ) => {



  const [ coordinates, setCoordinates ] = useState( { lats: 0, lngs: 0 } )

  // setting up the map with the image inside
  const MaxMapMBounds = [ [ 0.047356142676201576,
    -0.05648240856298255 ], [ -0.09911669921875, 0.05903846615873286 ] ]


  // building the map container
  const buildMap = () => {


    map = L.map( 'map', {
      crs: L.CRS.Simple,
      maxBounds: MaxMapMBounds,
      minZoom: 13,
      maxZoom: 15,
      maxBoundsViscosity: 0.0,

    } ).setView( [ 0, 0 ], 13 );



    // rendering the image inside the map 

    var imageBounds = [ [ 0.0484693481114499, -0.0652342559975141 ], [ -0.03143095517869242, 0.06518541415112039 ] ];

    const image = L.imageOverlay(
      masterplan,
      imageBounds,
      { className: 'image-map' },
    ).addTo( map );


    // Logging coordinates when clicking on the map
    map.on( 'click', function ( e ) {
      var coord = e.latlng;
      var lat = coord.lat;
      var lng = coord.lng;

      console.log( `latitude: ${ lat } and longitude: ${ lng } === [${ lat } ,
 ${ lng } ]` );
    } );


    return map
  }


  useEffect( () => {

    buildMap()
    addMarkers()
    renderMarkers()


    return () => {
      map.off();
      map.remove();

    }

  }, [ data, ] );



  // // adding markers from  FormMarker with draggable markers available

  const addMarkers = ( editedMarkerLatLng ) => {
    if ( addMarker )
    {

      let marker;


      // adding markers
      if ( isMarkerAdded === false )
      {
        let onDrag = function ( e ) {
          let latlng = marker.getLatLng();
          setCoordinates( { lats: latlng.lat, lngs: latlng.lng } )
          setFormData( { ...formData, coordinates: [ latlng.lat, latlng.lng ] } )

          // showing popup when dragging
          marker.bindPopup().openPopup()
        };



        let onClick = function ( e ) {

          setIsMarkerAdded( true )
          map.off( 'click', onClick ); //turn off listener for map click



          marker = L.marker( e.latlng, { icon: editMyIcon, draggable: true } ).addTo( map ).bindPopup( '<p>انا هنا يا جون</p>', { closePopupOnClick: false, autoClose: false, closeOnClick: false, } ).openPopup();


          // updating coordinates <p> on the ui
          setCoordinates( { lats: e.latlng.lat, lngs: e.latlng.lng } )

          // updating coordinates on the form
          setFormData( { ...setFormData, coordinates: [ e.latlng.lat, e.latlng.lng ] } )

          marker.on( 'drag', onDrag );

          // removing popup after adding the marker to markersList
          setGetMarkerObject( marker )
        };
        map.on( 'click', onClick );
      }





    }
  }





  //    rendering Markers of the Units inside the map &&& Focusing on selected unit by touching on its button link

  const renderMarkers = () => {


    data && data.map( ( link ) => {

      let marker



      if ( addMarker )
      {
        // rendering markers at add-marker page
        var container = L.DomUtil.create( 'div' );
        const createButton = ( label, container, className ) => {
          var btn = L.DomUtil.create( 'button', '', container );
          btn.setAttribute( 'type', 'button' );
          btn.setAttribute( 'class', className );
          btn.innerHTML = label;
          return btn;
        }

        const createParagraph = ( label, container, className ) => {
          var para = L.DomUtil.create( 'p', '', container );

          para.setAttribute( 'class', className );
          para.innerHTML = label;
          return para;
        }
        const editButton = createButton( 'Edit marker', container, 'edit-button' );
        const removeButton = createButton( 'Remove marker', container, 'remove-button' );
        const popupText = createParagraph( `Name: ${ link.name }`, container, 'popup-text' )

        L.DomEvent.on( editButton, 'click', () => handleEditMarker( marker ) );
        L.DomEvent.on( removeButton, 'click', () => handleRemoveMarker( marker ) );
        return marker = L.marker( L.latLng( link.markerPosition ), { icon: myIcon } ).addTo( map ).bindPopup( container )
      }



      // rendering markers at home page 

      marker = L.marker( L.latLng( link.markerPosition ), { icon: myIcon } ).addTo( map ).bindPopup( link.markerText )
      if ( link.name === linkName )
      {
        // Focusing on selected unit by touching on its button link


        let unitMarker = L.polygon( link.latlng, { fill: false, opacity: 0 } ).addTo( map )
        marker.openPopup()
        map.fitBounds( unitMarker.getBounds() )
        // setClicked( true )
      }
    } )

  }
  const handleEditMarker = ( marker ) => {
    setIsMarkerAdded( true )
    setEdit( true )
    const lat = marker._latlng.lat
    const lng = marker._latlng.lng
    let editedMarker = data.find( item => item.markerPosition[ 0 ] === lat && item.markerPosition[ 1 ] === lng )
    handleRemoveMarker( marker )


    setFormData( { ...editedMarker, coordinates: [ lat, lng ] } )

    marker = L.marker( [ lat, lng ], { icon: editMyIcon, draggable: true } ).addTo( map ).bindPopup( '<p>انا هنا يا جون</p>', { closePopupOnClick: false, autoClose: false, closeOnClick: false, } ).openPopup();


    let onDrag = function ( e ) {
      let latlng = marker.getLatLng();
      // setCoordinates( { lats: latlng.lat, lngs: latlng.lng } )
      setFormData( { ...editedMarker, coordinates: [ latlng.lat, latlng.lng ] } )

      // showing popup when dragging
      marker.bindPopup().openPopup()

    };
    marker.on( 'drag', onDrag );
    setGetMarkerObject( marker )




  }

  const handleRemoveMarker = ( marker ) => {

    const lat = marker._latlng.lat
    const lng = marker._latlng.lng

    let tempMarkers = data.filter( item => item.markerPosition[ 0 ] !== lat && item.markerPosition[ 1 ] !== lng )
    setMarkersList( tempMarkers )


  }


  return (
    <div className={ formCss ? 'map-container-form' : 'map-container' }>

      <div id="map"

      ></div>
      { formCss &&

        <p>coordinates: { `[ ${ coordinates.lats }, ${ coordinates.lngs } ]` }</p>
      }

    </div>
  );
};

export default MapContainer
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Accordion Component

import React, { useEffect } from 'react'
import { Accordion, useAccordionButton } from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';



const AccordionComp = ( { link, handleLink } ) => {



 return (

  <Accordion.Item eventKey={ link.id } onClick={ ( e ) => {
   handleLink( e, link.name )
  } } >
   <Accordion.Header >
    { link.name }
   </Accordion.Header>

   <Accordion.Body>

    <p><b> Type: </b>{ link.type }</p>
    <p><b> Area: </b>{ link.area } ( ㎡ )</p>
    <p><b> Price: </b> { link.price } (LE)</p>
    <p><b> Description: </b> { link.description }</p>
   </Accordion.Body>
  </Accordion.Item>


 )
}

export default AccordionComp
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

App Component

**the problem is when i click on the accordion, 'handleLink' fn is invoked to update linkName state using setLinkName.... when i comment out the setLinkName fn => accordion worked from the first click :( **

import './App.css';
import React, { useState, useEffect } from 'react'
import { data } from './data'
import MapContainer from './MapContainer'
import { Route, Switch } from "react-router-dom";
import FormMarker from './FormMarker';
import AccordionComp from './components/AccordionComp';
import { Accordion } from 'react-bootstrap';



const App = () => {

 const [ clicked, setClicked ] = useState( false )
 const [ linkName, setLinkName ] = useState( null )
 const initialMarkersList = localStorage.getItem( "markersList" )
  ? JSON.parse( localStorage.getItem( "markersList" ) )
  : [];


 const handleLink = ( e, linkName ) => {

  setLinkName( linkName )
 }


 const Home = () => {
  return (
   <div className="App">

    <div className="links">
     <Accordion   >
      { initialMarkersList && initialMarkersList.map( ( link ) => {

       return <AccordionComp key={ link.id } link={ link } setLinkName={ setLinkName } handleLink={ handleLink } setClicked={ setClicked } />
      } ) }
     </Accordion>
    </div>

    <MapContainer data={ initialMarkersList } linkName={ linkName } clicked={ clicked } setClicked={ setClicked } />
   </div>
  )
 }

 useEffect( () => {
  console.log( document.getElementsByClassName( 'accordion-collapse' )[ 0 ].classList );

 }, [] )


 console.log( linkName, 'linkname' );
 console.log( clicked, 'clickde' );

 return (
  <>
   <Switch>
    <Route path="/" exact component={ Home } />
    <Route path="/add-marker" exact component={ FormMarker } />

   </Switch >
  </>
 )
}

export default App;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

4
  • It would be better for us to help you if you place all the pieces in a codesandbox or something similar
    – kboul
    Commented Nov 2, 2021 at 13:36
  • great idea, here it is codesandbox.io/s/… Commented Nov 3, 2021 at 7:28
  • So you want to have also the accordion expanded when you click on it and centering the marker on the map?
    – kboul
    Commented Nov 3, 2021 at 10:37
  • 1
    yeah exactly, btw i solved it.. the states and the methods in the App comp are supposed to be put in the Home Comp which is already inside the App comp :) Commented Nov 3, 2021 at 10:40

1 Answer 1

0

The Answer is to move the states and the methods at the App comp to the Home comp where it is already inside App comp

 

import './App.css';
import React, { useState, useEffect } from 'react'
import { data } from './data'
import MapContainer from './MapContainer'
import { Route, Switch } from "react-router-dom";
import FormMarker from './FormMarker';
import AccordionComp from './components/AccordionComp';
import { Accordion } from 'react-bootstrap';



const App = () => {

 


 const Home = () => {
 
 
 const [ clicked, setClicked ] = useState( false )
 const [ linkName, setLinkName ] = useState( null )
 const initialMarkersList = localStorage.getItem( "markersList" )
  ? JSON.parse( localStorage.getItem( "markersList" ) )
  : [];


 const handleLink = ( e, linkName ) => {

  setLinkName( linkName )
 }
 
  return (
   <div className="App">

    <div className="links">
     <Accordion   >
      { initialMarkersList && initialMarkersList.map( ( link ) => {

       return <AccordionComp key={ link.id } link={ link } setLinkName={ setLinkName } handleLink={ handleLink } setClicked={ setClicked } />
      } ) }
     </Accordion>
    </div>

    <MapContainer data={ initialMarkersList } linkName={ linkName } clicked={ clicked } setClicked={ setClicked } />
   </div>
  )
 }

 useEffect( () => {
  console.log( document.getElementsByClassName( 'accordion-collapse' )[ 0 ].classList );

 }, [] )


 console.log( linkName, 'linkname' );
 console.log( clicked, 'clickde' );

 return (
  <>
   <Switch>
    <Route path="/" exact component={ Home } />
    <Route path="/add-marker" exact component={ FormMarker } />

   </Switch >
  </>
 )
}

export default App;

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Not the answer you're looking for? Browse other questions tagged or ask your own question.