Let me explain as short as I can: I have component written in next.js to fetch some data, and render it on MapBox map as heatmap.
Everything is working like a charm until I refresh page with rendered component:
- heatmap pins disappearing
- nothing in console
After I change whatever in component file (it can be string, whatever), and save that, on page heatmap pins appear.
Video can be viewed here video
I can paste code in next if someone is interested to help
- changing ways of fetching data
- static data loading from file
- caching
- updating and downgrading components
- dynamic import component
Code of component:
import mapboxgl from 'mapbox-gl'
import Button from '@mui/material/Button'
import Typography from '@mui/material/Typography'
import axios from 'axios'
import { useEffect, useState } from 'react'
import ReactMapboxGl from 'react-mapbox-gl'
import * as React from 'react'
const aqiColors = [
'#00FF00', // Good (0 - 50)
'#FFFF00', // Moderate (51 - 100)
'#FFA500', // Unhealthy for Sensitive Groups (101 - 150)
'#FF0000', // Unhealthy (151 - 200)
'#800080', // Very Unhealthy (201 - 300)
'#8B0000' // Hazardous (301 - 500)
]
const aqiRateWords = ['Dobar', 'Umjereno', 'Nezdrav za osjetljive skupine', 'Nezdrav', 'Veoma nezdrav', 'Opasan']
const AirQualityMap = () => {
const [showLegend, setShowLegend] = useState(false)
const [locations, setLocations] = useState([])
useEffect(() => {
mapboxgl.accessToken = 'pk.eyJ1IjoicmJlcmV0IiwiYSI6ImNsaXc2aG9lMzJkc2QzcW9jdTVqOWI5YXgifQ.ZOagw-O94DX7Dyxz9kuwGA'
const fetchData = async () => {
try {
const response = await axios.get('http://localhost:3001/api/all-stations', { next: { revalidate: 3600 } })
const data = response.data
setLocations(data)
localStorage.setItem('locations', JSON.stringify(data))
} catch (error) {
console.error('Error fetching data:', error)
}
}
fetchData()
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/dark-v11',
center: [18.434433, 43.89099],
zoom: 11
})
map.on('load', () => {
const heatmapData = {
type: 'FeatureCollection',
features: locations.map(location => ({
type: 'Feature',
properties: {
name: location.naziv_stanice,
aqi: parseFloat(location.aqi_izracun)
},
geometry: {
type: 'Point',
coordinates: location.koordinate.split(',').reverse()
}
}))
}
map.on('click', 'heatmapLayer', e => {
const { name, aqi } = e.features[0].properties
new mapboxgl.Popup().setLngLat(e.lngLat).setHTML(`<b>${name}</b><br>AQI: ${aqi}`).addTo(map)
})
map.on('mouseenter', 'heatmapLayer', () => {
map.getCanvas().style.cursor = 'pointer'
})
map.on('mouseleave', 'heatmapLayer', () => {
map.getCanvas().style.cursor = ''
})
// Add heatmap layer
map.addLayer({
id: 'heatmapLayer',
type: 'heatmap',
source: {
type: 'geojson',
data: heatmapData
},
paint: {
// Increase the heatmap weight based on AQI value
'heatmap-weight': ['interpolate', ['linear'], ['get', 'aqi'], 0, 0, 500, 1],
// Increase the heatmap intensity based on zoom level
'heatmap-intensity': ['interpolate', ['linear'], ['zoom'], 0, 1, 9, 3],
// Color ramp for heatmap
'heatmap-color': [
'interpolate',
['linear'],
['heatmap-density'],
0,
'rgba(0, 0, 255, 0)',
0.1,
aqiColors[0],
0.3,
aqiColors[1],
0.5,
aqiColors[2],
0.7,
aqiColors[3],
1,
aqiColors[4]
],
// Heatmap radius in pixels
'heatmap-radius': 120
}
})
})
return () => map.remove()
}, [])
const handleLegendToggle = () => {
setShowLegend(!showLegend)
}
return (
<div>
<div id='map' style={{ borderRadius: '6px', width: '100%', height: '80vh' }}>
<Button onClick={handleLegendToggle} variant='contained' style={{ left: '20px', top: '20px', zIndex: '999' }}>
<Typography variant='p' style={{ zIndex: '999', color: 'white' }}>
{showLegend ? 'Sakrij AQI legendu' : 'Pokaži AQI legendu'}
</Typography>
</Button>
{showLegend && (
<div
id='legend'
style={{
position: 'absolute',
bottom: 30,
left: 30,
background: '#fff',
padding: '10px',
border: '1px solid #ccc',
borderRadius: '4px',
fontSize: '10px'
}}
>
<div style={{ fontWeight: 'bold', marginBottom: '5px' }}>AQI legenda</div>
{aqiColors.map((color, index) => (
<div key={index}>
<span
style={{
backgroundColor: color,
width: '20px',
height: '20px',
display: 'inline-block',
marginRight: '5px'
}}
/>
{index * 50 + 1} - {(index + 1) * 50} ({aqiRateWords[index]})
</div>
))}
</div>
)}
</div>
<noscript>
<div>WebGL is not supported in your browser.</div>
</noscript>
</div>
)
}
export default AirQualityMap