Issue
I'm solving bugs on a website with maps API. I noticed that sometimes the markers wouldn't render, even though my fetch of info to the backend served the info. I have now noticed that the object that carries the info to the markers function is sometimes empty and I can't understand why. I'm new to react and didn't do the majority of the coding behind.
TestMap.tsx
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useState, useEffect, SetStateAction } from 'react';
import { useGeoReportState } from '../../context/GeoReportContext';
import { useMapContext } from '../../context/MapContext';
import { findMode, formSubmitGeoCoder, getSoilTypeColor } from '../../utils/Util';
import Navbar from '../Navbar';
import { notificationHandler, surveyLengthHandler } from '../../utils/ContextHandlers';
import { MarkerClusterer, SuperClusterAlgorithm } from '@googlemaps/markerclusterer';
import Icon from '../../img/point.png';
import FileSubmit from '../Form/FileSubmit';
import Form from '../Form/Form';
import ThankYou from '../Form/ThankYou';
import { IconContext } from 'react-icons';
import { AiOutlineLeft, AiOutlineRight, AiOutlineReload } from 'react-icons/ai';
import { GeoReport, Survey } from '../../types/GeoReportTypes';
import { SurveyData } from '../../types/SurveyTypes';
import { useNotificationDispatch } from '../../context/NotificationContext';
import { useSurveyDispatch } from '../../context/SurveyContext';
import PointData from '../Form/PointData';
import { useGoogleMap } from '@ubilabs/google-maps-react-hooks';
const TestMap: React.FC = () => {
const geoReportState = useGeoReportState();
const notificationDispatch = useNotificationDispatch();
const mapState = useMapContext();
const surveyDispatch = useSurveyDispatch();
const map = useGoogleMap();
let formData = {};
const [showFileSubmit, setShowFileSubmit] = useState(false);
const [showSliderForms, showSlider] = useState(false);
const [showThankYouForms, setShowThankYou] = useState(false);
const [surveys, setSurveys] = useState<Survey[]>([]);
const [email, setEmail] = useState<string>();
const [thisMarkers, setThisMarkers] = useState<google.maps.Marker[]>([]);
const [circles, setCircles] = useState<google.maps.Circle[]>([]);
const [pointDataArray, setPointDataArray] = useState<GeoReport | null>(null);
const [distanceBetween, setDistanceBetween] = useState<number | null>(null);
const [surveyNumber, setSurveyNumber] = useState(surveys.length + 1);
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [selectedImage, setSelectedImage] =useState<string>();
const incrementCount = () => {
setSurveyNumber(surveyNumber + 1);
};
const resetCount = () => {
setSurveyNumber(1);
};
//const [imgData, setImgData] = useState<any[]>([]);
//const fetchImages = async () => {
// await fetch(process.env.REACT_APP_BACKEND + '/api/driveimage/getimg')
// .then(r => r.json())
// .then((data) => {
// data.forEach((img: any) => {
// imgData.push(img);
// });
// setImgData(imgData);
// });
//};
//useEffect(() => { fetchImages(); }, []);
const [isMounted, setIsMounted] = useState(false);
useEffect(() => setIsMounted(true), []);
useEffect(() => {
if (!map) return;
if (isMounted && thisMarkers.length == 0) {
console.log('hi useEffect');
console.log(geoReportState);
Object.entries(geoReportState.map).map(([, reports]) => {
addMarkers(undefined, reports, thisMarkers, setThisMarkers, setPointDataArray);
addCircles(undefined, setPointDataArray, reports, circles, setCircles);
});
}
//console.log(thisMarkers);
thisMarkers.forEach(marker => {
marker.setMap(map);
});
}, [mapState]);
useEffect(() => {
if (!map) return;
map.addListener('zoom_changed', () => {
if (map.getZoom()! >= 11) {
thisMarkers.forEach(marker => {
marker.setMap(null);
});
circles.forEach(circle => {
circle.setMap(map);
});
}
if (map.getZoom()! < 11) {
thisMarkers.forEach(marker => {
marker.setMap(map);
});
circles.forEach(circle => {
circle.setMap(null);
});
}
});
}, [map?.getZoom()]);
thisMarkers.forEach(marker => {
marker.addListener('click', () => {
if (showSliderForms === true) {
showSlider(false);
}
});
});
const handleOpenSlider = () => {
showSlider(!showSliderForms);
if (pointDataArray) {
handleClosePointData();
}
};
const handleOpenFileSubmit = () => {
if (surveys.length === 0) {
notificationHandler(2, 'Por favor introduza valores válidos', notificationDispatch);
} else {
setShowFileSubmit(!showFileSubmit);
};
};
const handleOpenThankYou = () => {
setShowThankYou(!showThankYouForms);
};
const handleClosePointData = () => {
setPointDataArray(null);
setDistanceBetween(null);
};
const handleSubmitNew = () => {
setShowThankYou(!showThankYouForms);
setShowFileSubmit(!showFileSubmit);
setSurveys([]);
resetCount();
setSelectedFile(null);
setSelectedImage('');
};
const handleExit = () => {
setShowThankYou(!showThankYouForms);
setShowFileSubmit(!showFileSubmit);
showSlider(!showSliderForms);
setSurveys([]);
resetCount();
setSelectedFile(null);
setSelectedImage('');
};
const handleAddSurvey = (event: { preventDefault: () => void; }, lat: number, lng: number, nFreatico: boolean, inFreatico: number, nSPT: boolean, table: SurveyData[]) => {
event.preventDefault();
if (lat === 0 || lng === 0 || lat === undefined || lng === undefined || table.length === 0 || (nFreatico === true && (inFreatico === undefined || inFreatico === 0))) {
notificationHandler(2, 'Por favor introduza valores válidos', notificationDispatch);
} else {
if(nSPT) {
table.map(e => e.spt1 = 1000);
}
if (nFreatico === true) {
const dummy = {
lat: lat,
lng: lng,
nFreatico: nFreatico,
inFreatico: inFreatico,
table: table,
};
surveys.push(dummy);
} else {
const dummy = {
lat: lat,
lng: lng,
nFreatico: nFreatico,
inFreatico: 0,
table: table,
};
surveys.push(dummy);
};
setSurveys([...surveys]);
incrementCount();
notificationHandler(1, 'Sondagem submetida com sucesso', notificationDispatch);
surveyLengthHandler(surveys.length, surveyDispatch);
}
};
const handleClearSurveys = (event: { preventDefault: () => void; }) => {
event.preventDefault();
setSurveys([]);
resetCount();
};
const handleFileInput = (e: any, type: number) => {
e.preventDefault();
const file = e.target.files[0];
if (type === 1) {
if (file) {
if (file.type === 'application/pdf') {
setSelectedFile(file);
} else {
notificationHandler(
2,
'Por favor seleciona um ficheiro tipo PDF',
notificationDispatch,
);
}
}
} else if (selectedImage) {
setSelectedImage(file);
}
// else {
// notificationHandler(
// 2,
// 'Por favor seleciona uma imagem do tipo PNG ou JPEG',
// notificationDispatch
// );
//}
};
const handleUploadPDF = (e: any, filename: string) => {
e.preventDefault();
if (selectedFile) {
const formPDFData = new FormData();
formPDFData.append('filename', filename);
formPDFData.append('file', selectedFile);
fetch(process.env.REACT_APP_BACKEND + '/api/drive', {
method: 'POST',
body: formPDFData
});
//.then((response) => (console.log(response)))
//.catch((error) => console.log(error));
}
};
const handleUploadImage = (e: any, filename: string) => {
e.preventDefault();
if (selectedImage) {
const formImgData = new FormData();
// formImgData.append('filename', filename);
formImgData.append('image', selectedImage);
// fetch(process.env.REACT_APP_BACKEND + '/api/drive/image', {
// method: 'POST',
// body: formImgData,
// })
// .then((response) => console.log(response))
// .catch((error) => console.log(error));
}
};
const handleSubmitForm = async (e: { preventDefault: () => void; }) => {
e.preventDefault();
if (!surveys[0]) {
notificationHandler(
2,
'Não é possível submeter sem qualquer sondagem',
notificationDispatch
);
}
const lat = surveys[0].lat;
const lng = surveys[surveys.length - 1].lng;
const { adress, placeId } = await formSubmitGeoCoder(Number(lat), Number(lng), notificationDispatch);
if(email) {
if (selectedFile) {
const fileName = selectedFile.name;
const editedName = fileName.substring(0, fileName.length - 4);
if (selectedImage) {
// const oldImgName = selectedImage.name;
// const imgExtension = oldImgName.substring(oldImgName.length - 4, oldImgName.length);
// const imgName = `logo_${editedName}${imgExtension}`;
formData = { adress, placeId, lat, lng, email, surveys, fileName, selectedImage };
} else {
formData = { adress, placeId, lat, lng, email, surveys, fileName };
}
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
};
const response = await fetch(process.env.REACT_APP_BACKEND + '/api/report', requestOptions);
if (response.ok) {
handleOpenThankYou();
setSurveys([]);
resetCount();
handleUploadPDF(e, selectedFile.name);
if (selectedImage) {
// const oldImgName = selectedImage.name;
// const imgExtension = oldImgName.substring(oldImgName.length - 4, oldImgName.length);
// const imgName = `logo_${editedName}${imgExtension}`;
handleUploadImage(e, selectedImage);
};
}
if (!response.ok) {
notificationHandler(
2,
'Erro',
notificationDispatch
);
}
} else {
notificationHandler(
2,
'Para validação do relatório iremos necessitar do mesmo em formato PDF',
notificationDispatch
);
}
} else {
notificationHandler(
2,
'Insira um e-mail para poder submeter os dados',
notificationDispatch
);
}
};
return (
<>
<div className='flex flex-col w-full h-full'>
<Navbar setPointDataArray={setPointDataArray} setDistanceBetween={setDistanceBetween} />
{showSliderForms ? (
<>
<div className='z-30'>
<div className='bg-white sm:absolute sm:right-0 h-auto max-h-full sm:h-full sm:w-[40%] overflow-y-scroll'>
{showThankYouForms ? (
<>
<ThankYou />
<div className='justify-center mb-5 items-center flex space-x-2'>
<button
className='bg-blue-helica w-40 h-10 rounded-md text-xs text-white p-1'
onClick={handleSubmitNew}
>
Submeter novo estudo
</button>
<button
className='bg-blue-helica w-40 h-10 rounded-md text-xs text-white p-1'
onClick={handleExit}
>
Sair
</button>
</div>
</>
) : (
<>
<button
type='button'
className='bg-white border ml-4 mt-4'
onClick={handleOpenSlider}
>
<IconContext.Provider
value={{
color: '#000000',
className: 'h-6 sm:h-9 w-6 sm:w-9',
}}
>
<AiOutlineRight />
</IconContext.Provider>
</button>
<form className='h-auto'>
{showFileSubmit ? (
<>
<FileSubmit setEmail={setEmail} surveys={surveys} handleFileInput={handleFileInput} selectedFile={selectedFile} selectedImage={setSelectedImage} />
<div className='justify-center mb-5 items-center flex space-x-2'>
<button
className='bg-blue-helica w-40 h-10 rounded-md text-xs text-white p-1'
onClick={() => {
handleOpenFileSubmit();
}}
>
Passo Anterior
</button>
<button
className='bg-blue-helica w-40 h-10 rounded-md text-xs text-white p-1'
onClick={(event) => {
handleSubmitForm(event);
}}
>
Enviar
</button>
</div>
</>
) : (
<>
<Form handleOpenFileSubmit={handleOpenFileSubmit} handleAddSurvey={handleAddSurvey} handleClearSurveys={handleClearSurveys} surveyNumber={surveyNumber} />
</>
)}
</form>
</>
)}
</div>
</div>
</>
) : (
<>
{pointDataArray ? (
<>
<div className='z-30'>
<div className='bg-transparent right-[35%] absolute top-auto mt-3 h-auto max-h-full w-auto'>
<button
type='button'
className='bg-white border'
onClick={handleOpenSlider}
>
<IconContext.Provider
value={{
color: '#000000',
className: 'h-6 sm:h-9 w-6 sm:w-9',
}}
>
<AiOutlineLeft />
</IconContext.Provider>
</button>
</div>
</div>
</>
) : (
<>
<div className='z-30'>
<div className='bg-transparent absolute right-4 top-auto mt-3 mr-12 h-auto max-h-full w-auto'>
<button
type='button'
className='bg-white border mr-2 align-top'
title='Recarregar pontos de sondagem'
onClick={reload}
>
<IconContext.Provider
value={{
color: '#000000',
className: 'h-8 sm:h-10 w-8 sm:w-10',
}}
>
<AiOutlineReload />
</IconContext.Provider>
</button>
<button
type='button'
className='bg-white border'
onClick={handleOpenSlider}
>
<IconContext.Provider
value={{
color: '#000000',
className: 'h-12 sm:h-14 w-12 sm:w-14',
}}
>
<AiOutlineLeft />
</IconContext.Provider>
</button>
</div>
</div>
</>
)}
</>
)}
{pointDataArray ? (
<>
<div className='z-30'>
<div className='bg-white absolute sm:right-0 h-auto max-h-full sm:h-full sm:w-1/3 overflow-y-scroll'>
<PointData pointDataArray={pointDataArray} distance={distanceBetween} handleClosePointData={handleClosePointData} map={map} />
</div>
</div>
</>
) : (null)}
</div>
</>
);
};
function reload() {
window.location.reload();
}
function addMarkers(
map: google.maps.Map | undefined,
reports: GeoReport[],
thisMarkers: google.maps.Marker[],
setThisMarkers: React.Dispatch<React.SetStateAction<google.maps.Marker[]>>,
setPointDataArray: React.Dispatch<React.SetStateAction<GeoReport | null>>,
) {
//const reports2 = [
// {'lat':38.991846220258516,'lng':-8.962928379927412},
// {'lat':39.60136813907799, 'lng':-8.409397870167226},
// {'lat':39.60136813907797, 'lng':-8.409397870167224},
// {'lat':38.75296747910795, 'lng':-9.106130361105693},
// {'lat':38.637778, 'lng':-8.956944},
// {'lat':41.216111, 'lng':-8.677222},
// {'lat':40.915731, 'lng':-7.276742},
// {'lat':38.90057404501688, 'lng':-7.094068507607758}
//];
//console.log(reports);
let isVisited = false;
const markers = reports.map((report) => {
console.log(report.lat);
const marker = new google.maps.Marker({
position: new google.maps.LatLng(report.lat,report.lng),
//{ lat: report.lat, lng: report.lng },
map: map,
optimized: true,
//icon: Icon
});
marker.addListener('click', () => {
isVisited = !isVisited;
if (isVisited === true) {
setPointDataArray(report);
}
});
thisMarkers.push(marker);
setThisMarkers([...thisMarkers]);
new MarkerClusterer({
markers: thisMarkers,
map,
algorithm: new SuperClusterAlgorithm({ radius: 200 }),
});
return marker;
});
//console.log(markers);
}
const addCircles = (
map: google.maps.Map | undefined,
setPointDataArray: React.Dispatch<React.SetStateAction<GeoReport | null>>,
reports: GeoReport[], circles: google.maps.Circle[],
setCircles: React.Dispatch<SetStateAction<google.maps.Circle[]>>,
) => {
let isVisited = false;
const addresses: string[] = [];
reports.map((report: GeoReport) => {
report.surveys.map(survey => {
survey.table.map(data => {
addresses.push(data.soilType);
});
});
let color = '#FFFFFFF';
const soilType = findMode(addresses);
if (soilType) {
color = getSoilTypeColor(soilType);
}
const marker = new google.maps.Circle({
center: { lat: report.lat, lng: report.lng },
radius: 500,
strokeColor: color,
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: color,
fillOpacity: 0.35,
map,
});
marker.addListener('click', () => {
isVisited = !isVisited;
if (isVisited === true) {
setPointDataArray(report);
}
});
circles.push(marker);
setCircles([...circles]);
return marker;
});
};
export default TestMap;
GeoReportContext.tsx
import React, { createContext, useContext, useState, useEffect } from 'react';
import { geoReportDispatch, geoReportState } from '../types/ContextTypes';
import { GeoReport } from '../types/GeoReportTypes';
const initialState: geoReportState = {
map: {},
length: 0,
styles: [],
};
const GeoReportContext = createContext<geoReportState | undefined>(undefined);
const GeoReportDispatchContext = createContext<geoReportDispatch | undefined>(undefined);
export const useGeoReportState = () => {
const state = useContext(GeoReportContext);
if (!state) {
throw new Error('GeoReportContext must be used within a provider.');
}
return state;
};
export const useGeoReportDispatch = () => {
const dispatch = useContext(GeoReportDispatchContext);
if (!dispatch) {
throw new Error('GeoReportDispatchContext must be used within a provider.');
}
return dispatch;
};
export const GeoReportProvider = ({ children }: { children: React.ReactNode }) => {
const [state, setState] = useState(initialState);
const dispatch = {
setMap: (map: { [key: string]: GeoReport[] }) => setState((prevState) => ({ ...prevState, map })),
setLength: (length: number) => setState((prevState) => ({...prevState, length})),
setStyles: (styles: string[]) => setState((prevState) => ({...prevState, styles})),
};
const [markers, setMarkers] = useState<Array<GeoReport>>([]);
const copyOfMarkers = markers;
const fetchReports = async () => {
const response = await fetch(process.env.REACT_APP_BACKEND + '/api/report', { method: 'GET', cache: 'no-cache' })
.then((response) => response.json());
response.forEach((report: GeoReport) => {
copyOfMarkers.push({
id: report.id,
lat: report.lat,
lng: report.lng,
adress: report.adress,
placeId: report.placeId,
surveys: report.surveys,
fileName: report.fileName,
imgName: report.imgName
});
});
//const json = await response.json();
//const copyOfMarkers = markers;
//json.forEach((report: GeoReport) => {
// copyOfMarkers.push({
// id: report.id,
// lat: report.lat,
// lng: report.lng,
// adress: report.adress,
// placeId: report.placeId,
// surveys: report.surveys,
// fileName: report.fileName,
// imgName: report.imgName
// });
//});
setMarkers([...copyOfMarkers]);
//const newMarkers = json.map((report: GeoReport) => ({
// id: report.id,
// lat: report.lat,
// lng: report.lng,
// adress: report.adress,
// placeId: report.placeId,
// surveys: report.surveys,
// fileName: report.fileName,
// imgName: report.imgName
//})
//);
//console.log(newMarkers);
//setMarkers(newMarkers);
//console.log('2', markers);
await dispatch.setMap(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
markers.reduce((acc: any, curr: any) => {
(acc[curr.adress] = acc[curr.adress] || []).push(curr);
return acc;
}, {} as { [key: string]: GeoReport[] }),
);
//console.log('markers', markers);
};
useEffect(() => {
fetchReports();
console.log('useGeoReportState', state);
}, []);
useEffect(() => {
console.log('useGeoReportState', state);
console.log('markers', markers);
}, []);
return (
<GeoReportContext.Provider value={state}>
<GeoReportDispatchContext.Provider value={dispatch}>
{children}
</GeoReportDispatchContext.Provider>
</GeoReportContext.Provider>
);
};
Can someone please help me regarding this issue? Thanks in advance
Solution
In TestMap.tsx
, Add geoReportState
as a useEffect(line: 86) dependency. As it is used by addMarkers
and addCircles
functions.
Also the codesandbox you added is private.
Answered By - nisargrbhatt
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.