habbahans
habbahans

Reputation: 132

Click handler on mapboxGL not respondig to updated state using react hooks with typescript

I am working on this simple hiking application, with display a map in a modal where i want to set a start point and end point in the modal components state, and then update the parents state.

The problem is that the click handler in the modal does not "see" the updated state.

If i console log the state outside of the click handler, it gives me the updated state.

Im using mapboxGL js, and i wonder if someone knows why this is happening? I am thinking maybe it as something to do with the 'click' event, since it not react onClick event?

Here is the code for the modal component:

export const MapModalContent = ({
  newHike, setNewHike, setShowMapModal,
}: MapModalProps) => {
  const [map, setMap] = useState<mapboxgl.Map>();
  const [startCoords, setStartCoords] = useState<LngLat>();
  const [endCoords, setEndCoords] = useState<LngLat>();
  const mapRef: React.MutableRefObject<HTMLDivElement | null> = useRef(null);

  const [helperString, setHelperString] = useState<IHelperString>(helperStrings[0]);

  const [startMarker] = useState(new mapboxgl.Marker());
  const [endMarker] = useState(new mapboxgl.Marker());
  const [startPointIsSet, setStartPointIsSet] = useState<boolean>(false);
  const [endPointIsSet, setEndPointIsSet] = useState<boolean>(false);

  // initializes map
  useEffect(() => {
    if (mapRef.current && !map) {
      setMap(new mapboxgl.Map({
        accessToken: MAPBOX_ACCESS_TOKEN,
        container: mapRef.current,
        style: 'mapbox://styles/mapbox/outdoors-v11',
        center: [10.748503539483494, 59.92003719905571],
        zoom: 10,
      }));
    }
  }, [mapRef]);

  // adds controls and click listener to map
  useEffect(() => {
    if (map) {
      addControls({ to: map });

      map.on('click', (e) => {
        handleSetMarker(e);
      });
    }
  }, [map]);

  // these effects console the updated state as wanted
  useEffect(() => {
    console.log('Start coord: ', startCoords);
  }, [startCoords]);

  useEffect(() => {
    console.log('End coord: ', endCoords);
  }, [endCoords]);

  useEffect(() => {
    console.log('Start is set: ', startPointIsSet);
  }, [startPointIsSet]);

  useEffect(() => {
    console.log('End is set: ', endPointIsSet);
  }, [endPointIsSet]);

  // Todo: Remove this..
  setTimeout(() => {
    if (map) map.resize();
  }, 500);

  // this is the click handler that does not respond to state changes
  const handleSetMarker = (e: mapboxgl.MapMouseEvent & mapboxgl.EventData) => {
    console.log('👆', `start point: ${startPointIsSet}`, `end point: ${endPointIsSet}`);

    if (!startPointIsSet) {
      console.log('Start point not set.. Gonna set it now!');
      // setStartCoords(e.lngLat);
      startMarker.setLngLat(e.lngLat).addTo(map!);
      setStartCoords(e.lngLat);
      setStartPointIsSet(true);
    }

    if (startPointIsSet && !endPointIsSet) {
      console.log('Start point is set! Setting end point..');
      endMarker.setLngLat(e.lngLat).addTo(map!);
      setEndCoords(e.lngLat);
      setEndPointIsSet(true);
    }
  };

  const handleButtonTap = () => {
    if (startCoords) {
      // displays a new message to the user after setting start point
      setHelperString(helperStrings[1]);
    } else {
      console.warn('No start coords set!');
    }

    // sets parents state
    if (startCoords && endCoords) {
      setNewHike({
        ...newHike,
        start_point: getPointString({ from: startCoords }),
        end_point: getPointString({ from: endCoords }),
      });
      setShowMapModal(false);
    } else {
      console.warn('Some coords is null!', startCoords, endCoords);
    }
  };

  return (
    <>
      <MapContainer ref={mapRef} />
      <p style={{ margin: '1em auto 1em auto' }}>{ helperString.sentence }</p>
      <IonButton onClick={handleButtonTap}>{ helperString.button }</IonButton>
    </>
  );
};


I've tried lifting the state up to the parent, but it gave me the exact same result.

I've also tried adding two separate click events to the map with no luck.

And I gave it a try with the 'react-mapbox-gl' wrapper, but the same problem arose.

Upvotes: 3

Views: 1287

Answers (1)

windowsill
windowsill

Reputation: 3649

It looks like because your handler is attached as a callback, it closes over the React state.

      map.on('click', (e) => {
        handleSetMarker(e);
      });

Try useCallback with the proper dependencies.

  const handleSetMarker = useCallback((e: mapboxgl.MapMouseEvent & mapboxgl.EventData) => {
    console.log('👆', `start point: ${startPointIsSet}`, `end point: ${endPointIsSet}`);

    if (!startPointIsSet) {
      console.log('Start point not set.. Gonna set it now!');
      // setStartCoords(e.lngLat);
      startMarker.setLngLat(e.lngLat).addTo(map!);
      setStartCoords(e.lngLat);
      setStartPointIsSet(true);
    }

    if (startPointIsSet && !endPointIsSet) {
      console.log('Start point is set! Setting end point..');
      endMarker.setLngLat(e.lngLat).addTo(map!);
      setEndCoords(e.lngLat);
      setEndPointIsSet(true);
    }
  }, [startPointIsSet, endPointIsSet, endMarker, startMarker, map]);

Upvotes: 1

Related Questions