mapbox-gl-geocoder icon indicating copy to clipboard operation
mapbox-gl-geocoder copied to clipboard

Geocoder input reinitializes repeatedly in dev mode (react + useEffect)

Open dispatchr1 opened this issue 4 years ago • 4 comments

System Info

OS macOS 10.13.6 High Sierra Node version 14.15 Libraries:

  • mapbox-gl
  • MapboxGeocoder
  • create-react-app
  • tailwindcss@2
  • craco

Symptoms

I'm using the geocoder js library in a react application and seeing that the input is rendered each time my development server restarts (create react app fast refresh). I believe the appropriate enhancement is to provide a clean up function to remove the geocoder on component unmount, similar to the map.remove() function provided by mapboxgl.Map() as per the react example repo

Code sample

import { useEffect } from 'react'
import mapboxgl from 'mapbox-gl'
import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder'
import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css'

const geocoder = new MapboxGeocoder({
  accessToken: process.env.REACT_APP_MB_TOKEN,
  mapboxgl: mapboxgl
})

export default function Autocomplete() {
  useEffect(() => {
    geocoder.addTo('#location')
    geocoder.on('result', function ({ result }) {
      console.dir(result)
    })
  }, [])
  return <div id='location' />
}

Screenshot

Autocomplete

dispatchr1 avatar Dec 13 '20 21:12 dispatchr1

@dispatchr1 did you solve it ?

Digital-Coder avatar Jul 31 '22 12:07 Digital-Coder

Also having this issue.

wwwhatley avatar Aug 05 '22 20:08 wwwhatley

Just like we add children to our container with the addTo method, we can remove them using the Node.removeChild API. This would live in the cleanup callback of our useEffect hook.

  useEffect(() => {
    geocoder.addTo("#geocoder");

    return () => {
      const parentContainer = document.getElementById("geocoder");
      const geoChild = document.getElementsByClassName(
        "mapboxgl-ctrl-geocoder"
      );

      if (!parentContainer || !geoChild) return;
      parentContainer.removeChild(geoChild[0]);
    };
  }, []);

If you are weary about a change in className breaking your implementation. Then you can loosen the coupling by removing all children on every cleanup. Below I should only the cleanup callback.

return () => {
      const element = document.getElementById("geocoder");

      if (!element) return;

      while (element.firstChild) {
        element.removeChild(element.firstChild);
      }
    };

JeffThorslund avatar Sep 21 '22 19:09 JeffThorslund

This is how I solved.


import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder';
import mapboxgl from 'mapbox-gl';
import { useEffect, useRef, useState } from 'react';

mapboxgl.accessToken = import.meta.env.APP_MAPBOX_PUBLIC_TOKEN;

const geocoder = new MapboxGeocoder({
    accessToken: mapboxgl.accessToken,
    types: 'country,region,place,postcode,locality,neighborhood'
});

interface PlaceAutocompleteProps {

}

export default function PlaceAutocomplete(_props: PlaceAutocompleteProps) {
    const autocompleteRef = useRef<HTMLDivElement>(null);
    const [result, setResult] = useState<any>(null);

    useEffect(() => {
        const onResult = (e: any) => {
            setResult(e.result);
        }

        const onClear = () => {
            setResult(null);
        }

        geocoder.addTo(autocompleteRef.current);
        geocoder.on('result', onResult);
        geocoder.on('clear', onClear);

        return () => {
            geocoder.off('result', onResult);
            geocoder.off('result', onClear);

            if(autocompleteRef.current) {
                autocompleteRef.current.innerHTML = ''; // <-------------- This line does the trick
            }
        }
    }, []);


    return (<>
        <div className='w-full' ref={autocompleteRef}></div>
        <pre>{JSON.stringify(result, null, 2)}</pre>
    </>)
}

lekhnath avatar Nov 06 '23 10:11 lekhnath