Mapbox-gl-js: React Hooks and map handlers do not have access to any of the changes to the app's useState or useSelector variables

Created on 24 Apr 2020  路  4Comments  路  Source: mapbox/mapbox-gl-js

mapbox-gl-js version:

Question

I am currently working on using mapbox-gl-js with React Hooks and the changes to the useSelector and useState variables do not seem to be available to the map event handlers when they are triggered. Instead, the useSelector/useStates are what they were when the handlers were added to the map. I am currently working around it by adding more states that trigger useEffects but it feels a tad janky.
Is there a way to add/bind context to the event handlers?
Thank you

Links to related documentation

Most helpful comment

The problem is related to React and not Mapbox. I have better understood the problem thanks to this post https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559 or this one https://stackoverflow.com/questions/54806681/react-hook-saved-state-in-event-listener-wrong/56458121#56458121.
I'm hoping this can help

Example usage:

  ...
  const [points, _setPoints] = useState([]);
  const pointsRef = useRef(points);
  const setPoints = (data) => {
    pointsRef.current = data;
    _setPoints(data);
  };

  useEffect(() => {
    // ... init map ...
    map.on("load", function () {
      map.on("click", function (e) {
        let statePoints = [...pointsRef.current]
        statePoints.push(e.lngLat)
        setPoints(statePoints)
      })
    });
  }, []);

  useEffect(() => {
    console.log(points)
  }, [points])
  ...

All 4 comments

I'm not 100% sure what exactly you want to do with the event handlers. I assume you're listening to events with something like map.on('click', function () {...});. In that case, unfortunately there is not really any way to add or bind context. The only thing you can manipulate is the callback function.

I'm not very familiar with React Hooks in general, but useState and useSelector appear to be used to retrieve state. If these aren't being updated, that suggests to me a disconnect between GL JS and React/Redux's built-in state functionality. GL-JS does not officially support React or have any official integration (you can see a detailed discussion of whether or not we should have an official React wrapper in https://github.com/mapbox/mapbox-gl-js/issues/6090). You could try https://github.com/alex3165/react-mapbox-gl as it is a popular React wrapper around GL-JS that attempts to sync the state with the map. If you don't want to go that route, I'd recommend asking this question on Stack Overflow or contacting Mapbox Support for a more detailed answer than we can offer through this channel. Thanks for using Mapbox!

I had the same issue what you need to do is to save the reference to the listener function.

A use effect solves can solve it. Every time any variable you use inside the click function demands the function to be remade.
in this case, if my hub or sites are changed I'll need to deregister the previous listener and register a new one.

useEffect(() => {
    if (!map) {
      return
    }
    const evType = "click"
    const listener: (ev: MapEventType & EventData) => void = e => {
      setMap(newMap)
      onClick(e.lngLat)
    }
    const newMap = map.on(evType, listener)
    return () => {
      map.off(evType, listener)
    }
  }, [map, hub, sites])

I've found that you can use the Redux hook 'useStore' to get the value of anything you want from the redux store and you can use it in the functions you add to different mapbox event listeners without the need to trigger a useEffect.
Hope that helps!

The problem is related to React and not Mapbox. I have better understood the problem thanks to this post https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559 or this one https://stackoverflow.com/questions/54806681/react-hook-saved-state-in-event-listener-wrong/56458121#56458121.
I'm hoping this can help

Example usage:

  ...
  const [points, _setPoints] = useState([]);
  const pointsRef = useRef(points);
  const setPoints = (data) => {
    pointsRef.current = data;
    _setPoints(data);
  };

  useEffect(() => {
    // ... init map ...
    map.on("load", function () {
      map.on("click", function (e) {
        let statePoints = [...pointsRef.current]
        statePoints.push(e.lngLat)
        setPoints(statePoints)
      })
    });
  }, []);

  useEffect(() => {
    console.log(points)
  }, [points])
  ...
Was this page helpful?
0 / 5 - 0 ratings