LowCool
LowCool

Reputation: 1411

Mapbox map is getting loaded before useEffect returns data

I have started learning react hooks and I want to load map on initial render with cooridnates which I get from the backend, but somehow my map is getting rendered before my api give the data back, how do I make sure my map will wait till data is return ? do I need to put any condition on that?

below is api code

import React, { useState, useEffect, useRef } from "react";
import { useDispatch, useSelector } from 'react-redux';
import mapboxgl from 'mapbox-gl';
import { getBikeInfo, mapDetails } from './features/counter/getInfo/getDetails.js'

function App() {
    const dispatch = useDispatch();

    const dataToDisplay = useSelector(mapDetails);
    
    const mapContainer = useRef(null);

    mapboxgl.accessToken = 'pk.eyJ1IjoidmloYW5nMTYiLCJhIjoiY2ttOHowc2ZhMWN2OTJvcXJ0dGpiY21pNyJ9.hK5Wxwby89E7tKWoBoY5bg';

    useEffect(() => {
        dispatch(getBikeInfo())

        var map = new mapboxgl.Map({
            container: mapContainer.current,
            style: 'mapbox://styles/mapbox/light-v10',
            center: [-96, 37.8],
            zoom: 3
        });

        console.log('mapdetails:' + mapDetails)
        console.log('data display:' + dataToDisplay)
        
        map.on('load', function () {
            // Add an image to use as a custom marker
            map.loadImage(
                'https://docs.mapbox.com/mapbox-gl-js/assets/custom_marker.png',
                function (error, image) {
                    if (error) throw error;
                    map.addImage('custom-marker', image);
                    // Add a GeoJSON source with 2 points
                    map.addSource('points', {
                        'type': 'geojson',
                        'data': {
                            'type': 'FeatureCollection',
                            'features': dataToDisplay
                        }
                    });

                    // Add a symbol layer
                    map.addLayer({
                        'id': 'points',
                        'type': 'symbol',
                        'source': 'points',
                        'layout': {
                            'icon-image': 'custom-marker',
                            // get the title name from the source's "title" property
                            'text-field': ['get', 'title'],
                            'text-font': [
                                'Open Sans Semibold',
                                'Arial Unicode MS Bold'
                            ],
                            'text-offset': [0, 1.25],
                            'text-anchor': 'top'
                        }
                    });
                }
            );
        });
    }, []);


    return (
        <div className="district-map-wrapper">
            <div id="districtDetailMap" className="map">
                <div style={{ height: "100%" }} ref={mapContainer}>

                </div>
            </div>
        </div>
    );
}

export default App;

below is my updated code:

useEffect(() => {
    if (dataToDisplay.length != 0) {
        loadtheMap
    }
}, []);

but after some refresh I see data is not getting populated and setting dataDisplay to [].

update 1: based on suggestion I have updated my code like this

if(!dataLoaded) { // do not render anything if you're not ready
  return ( 
  <div className="hidden">
  <div id="districtDetailMap" className="map">
      <div style={{ height: "100%" }} ref={mapContainer}>

      </div>
  </div>
</div>);
}

where className="hidden" is define in App.css like below

.hidden{
  display: none;
}

but still I am on same issue

as requested PFB my sandbox link

codesandbox

Upvotes: 1

Views: 2769

Answers (1)

dkuznietsov
dkuznietsov

Reputation: 298

Since you're using useEffect you are basically guaranteed that your component will render once before it even initiates the query

If you want to ensure your component won't be rendered, use some flag to indicate that data is ready, set it to true after you set up your Map and conditionally render some fallback ui while this flag is false

function App() {
    const [dataLoaded, setDataLoaded] = useState(false); // introduce the flag
    const dispatch = useDispatch();

    const dataToDisplay = useSelector(mapDetails);
    
    const mapContainer = useRef(null);

    mapboxgl.accessToken = 'pk.eyJ1IjoidmloYW5nMTYiLCJhIjoiY2ttOHowc2ZhMWN2OTJvcXJ0dGpiY21pNyJ9.hK5Wxwby89E7tKWoBoY5bg';

    useEffect(() => {
        dispatch(getBikeInfo())

        var map = new mapboxgl.Map({
            container: mapContainer.current,
            style: 'mapbox://styles/mapbox/light-v10',
            center: [-96, 37.8],
            zoom: 3
        });

        console.log('mapdetails:' + mapDetails)
        console.log('data display:' + dataToDisplay)
        
        map.on('load', function () {
            // Add an image to use as a custom marker
            map.loadImage(
                'https://docs.mapbox.com/mapbox-gl-js/assets/custom_marker.png',
                function (error, image) {
                    if (error) throw error;
                    map.addImage('custom-marker', image);
                    // Add a GeoJSON source with 2 points
                    map.addSource('points', {
                        'type': 'geojson',
                        'data': {
                            'type': 'FeatureCollection',
                            'features': dataToDisplay
                        }
                    });

                    // Add a symbol layer
                    map.addLayer({
                        'id': 'points',
                        'type': 'symbol',
                        'source': 'points',
                        'layout': {
                            'icon-image': 'custom-marker',
                            // get the title name from the source's "title" property
                            'text-field': ['get', 'title'],
                            'text-font': [
                                'Open Sans Semibold',
                                'Arial Unicode MS Bold'
                            ],
                            'text-offset': [0, 1.25],
                            'text-anchor': 'top'
                        }
                    });
                    setDataLoaded(true); // set it to true when you're done
                }
            );
        });
    }, []);

    return (
        <div className="district-map-wrapper" style={dataLoaded ? undefined : {display: 'none'}}>
            <div id="districtDetailMap" className="map">
                <div style={{ height: "100%" }} ref={mapContainer}>

                </div>
            </div>
        </div>
    );
}

Upvotes: 1

Related Questions