how can i use Google maps with primereact - reactjs

I am trying to use the GMap component that is part of the primereact library, but i keep getting this error :
Unhandled Runtime Error ReferenceError: google is not defined
I am working on a next.js app, here is the simple code i copied/pasted from primerreact library:
import { GMap } from 'primereact/gmap';
const DealershipLocation = () => {
const options = {
center: {lat: 36.890257, lng: 30.707417},
zoom: 12
};
return (
<div>
<GMap options={options} style={{width: '100%', minHeight: '320px'}} />
</div>
)
}
export default DealershipLocation

From the GMAP showcase it looks like you aren't loading Google Maps JS correctly.
Add these functions... where key=XXX is your Google Maps key
const loadGoogleMaps = (callback) => {
const existingScript = document.getElementById('googleMaps');
if (!existingScript) {
const script = document.createElement('script');
script.src = 'https://maps.google.com/maps/api/js?key=XXX';
script.id = 'googleMaps';
script.async = true;
script.defer = true;
document.body.appendChild(script);
script.onload = () => {
if (callback) callback();
};
}
if (existingScript && callback) callback();
};
const removeGoogleMaps = () => {
const mapScript = document.getElementById('googleMaps');
if (mapScript) {
mapScript.parentNode.removeChild(mapScript);
const head = document.getElementsByTagName('head')[0];
const scripts = head.getElementsByTagName('script');
for (let i = 0; i < scripts.length; i++) {
let script = scripts[i];
let src = script.src;
if (src.startsWith('https://maps.google.com/maps')) {
head.removeChild(script);
}
}
}
};
Then load it in a React Hook..
const [googleMapsReady, setGoogleMapsReady] = useState(false);
useEffect(() => {
loadGoogleMaps(() => {
setGoogleMapsReady(true);
});
return () => {
removeGoogleMaps();
}
}, []);

Related

Why am I getting this error: Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks?

I'm new to react but I've encountered enough errors to know not to call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks
I'm trying to import data from mongodb atlas into my app.
From the codes written below, I don't think it has violated the rule. Any help would be great thx!
import { useState, useEffect } from "react";
import axios from "axios";
const Events = () => {
const [allEvents, setEvents] = useState([]);
const fetchEvents = async () => {
try {
const { data } = await axios.get(`http://localhost:5000/get/events`);
return data
} catch (err) {
console.log(err);
}
};
useEffect(() => {
fetchEvents();
}, []);
};
export default Events;
function CalendarComponent() {
//useState declarations
const [newEvent, setNewEvent] = useState({
id: "",
title: "",
start: "",
action: "",
});
const [allEvents, setEvents] = useState(events);
const [toggleAdd, setToggleAdd] = useState(false);
const [msg, setMsg] = useState("");
//handle creating new event on button click
function handleAddSlot() {
const title = newEvent.title;
const start = newEvent.start;
const end = start;
let success = 0;
if (title && newEvent.action === control.ADD) {
// add new event into the list
const id = allEvents.length;
if(data.Events.some(i=>i["Serial Number"]===title)){
// let newArr = [...data.Events]
// const res = newArr.find(e=>e["Serial Number"]===title)["Scheduled Sample Date"]
// console.log(res);
// res.push({id:res.length,start,end})
data.Events.map(item=>item["Serial Number"]===title?{...item}["Scheduled Sample Date"].push({"id":{...item}["Scheduled Sample Date"].length,start}):item)
}
else{
setEvents((prev) => [...prev, { start, end, title }]);
}
setToggleAdd(true);
console.log(data.Events)
success = 1;
}
if (newEvent.action === control.UPDATE) {
// find id of existing event to update
setEvents((currEvents) =>
currEvents.map((event) => {
if (event.id === newEvent.id) {
return {
...event,
start: start,
end: start,
title: title,
};
} else {
return event;
}
})
);
success = 1;
console.log(allEvents);
}
// if action successful, close modal and clear all fields
if (success === 1) {
setToggleAdd(false);
clearFields();
}
}
// handle when user select existing scope event
function handleSelectEvent(event) {
setMsg("Update Scope");
const start = new Date(event.start);
setNewEvent({
...newEvent,
action: control.UPDATE,
title: event.title,
start,
id: event.id,
});
setToggleAdd(true);
}
//handle calendar slot click
function handleClick(e) {
newEvent.start = e.start;
newEvent.action = control.ADD;
setMsg("Add New Scope");
if (
// ? Can be better written
document.getElementById("id01")?.classList.contains("setDisplay") === true
) {
setToggleAdd(true);
}
}
//handle modal close when user clicks on x
function toggleClick() {
if (toggleAdd === true) {
setToggleAdd(false);
clearFields();
}
}
//function to clear all fields
function clearFields() {
newEvent.start = "";
newEvent.title = "";
}
return (
<div className={`calendar-container`}>
<AddModal
id="id01"
toggle={toggleAdd}
newEvent={newEvent}
toggleClick={toggleClick}
handleSelectSlot={handleAddSlot}
setNewEvent={setNewEvent}
msg={msg}
/>
<Calendar
localizer={localizer}
events={allEvents}
popup
defaultView="month"
longPressThreshold={1}
views={["month"]}
eventPropGetter={(event) => {
const identifier = data.Events.find(
(item) => item["Type"] === event.type
);
const backgroundColor = identifier
? data.Color.find((item) => item[`${identifier.Type}`])[
`${identifier.Type}`
]
: "";
return { style: { backgroundColor } };
}}
onSelectEvent={handleSelectEvent}
onSelectSlot={(e) => handleClick(e)}
selectable
style={{ height: 500, margin: "30px" }}
components={{
toolbar: CustomToolbar,
}}
/>
<div className="calendar-sidebar">
<Button text="Add New" button="button" onClick={handleClick} />
<Log type='Schedule' />
</div>
</div>
);
}
I've attached the CalendarComponent. I can't really seem to find the problem in CalendarComponent myself. Hopefully it's not in the react big calendar package and I'm not missing something

React native custom hook return null in the first time using react-native-geolocation-services custom

I am trying to create a custom hook to get user's current location. I am using react-native-geolocation-services.
It returns null for the first time.
However, when I try to re-run the app. The geo data shows again.
Is this issue happening in asyn data?
Am I wrongly implemented the usestate so that the data didn't show in the first time?
Map component
import {useCurrentLocation} from '../queries/getCurrentLocation';
const Map = () => {
const {coordinate, watchError} = useCurrentLocation();
console.log('data',coordinate)
return <View style={styles.container}><MapView /></View>;
};
Custome Hook
import React, {useRef, useState, useEffect } from 'react';
import Geolocation, {watchPosition} from 'react-native-geolocation-service';
import useLocationPermission from '../hooks/useLocationPermission';
export const useCurrentLocation = () => {
const [coordinate, setCoordinate] = useState(null);
const [watchError, setWatchError] = useState(null);
const watchId = useRef(null);
const {hasPermission, hasPermissionError} = useLocationPermission();
const startWatch = () => {
if (!hasPermission) return;
watchId.current = Geolocation.watchPosition(
position => {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
const speed = position.coords.speed;
setCoordinate({latitude, longitude, speed});
},
error => {
setWatchError(error);
},
{
accuracy: {
android: 'high',
//TODO config to ios
//ios: 'best',
},
enableHighAccuracy: true,
distanceFilter: 0,
interval: 20000,
fastestInterval: 2000,
},
);
};
const stopWatch = () => {
if (watchId.current == null) return;
Geolocation.clearWatch(watchId.current);
watchId.current = null;
};
useEffect(() => {
if (hasPermission) {
getCurrentCoordinate(coordinate);
}
startWatch();
return () => {
stopWatch();
};
}, [coordinate]);
return {coordinate, watchError};
};
const getCurrentCoordinate = coordinate => {
Geolocation.getCurrentPosition(position => {
coordinate = position;
});
return coordinate;
};

React Hook useEffect has a missing dependency: 'getNewPins'

I have a small project, to fetch images from Unsplash, even ı check many times, I still have one problem with my code, I am running my code, but it always gives this error, but I don't get it. Any idea will be appreciated.
App.js:
function App() {
const [pins, setNewPins] = useState([])
const getImages = (term) => {
return unsplash.get ("https://api.unsplash.com/search" , {
params: {
query: term
}
});
};
const onSearchSubmit = (term) => {
getImages(term).then((res) => {
let results = res.data.results;
let newPins = [
...results,
...pins
]
newPins.sort(function(a,b) {
return 0.5 - Math.random();
});
setNewPins(newPins);
});
};
const getNewPins = () => {
let promises = [];
let pinData = [];
let pins = ['Istanbul','cats','sky','lake','nature']
pins.forEach((pinTerm) => {
promises.push (
getImages(pinTerm).then((res) => {
let results = res.data.results;
pinData = pinData.concat(results);
pinData.sort(function(a,b) {
return 0.5 - Math.random();
});
});
);
});
Promise.all(promises).then(()=> {
setNewPins(pinData);
});
};
useEffect(() => {
getNewPins();
}, []);
return (
<div className="app">
<Header onSubmit={onSearchSubmit}/>
<Mainboard pins={pins}/>
</div>
)}
export default App;
The getNewPins method is re-created everytime in your app after each render and you're using it inside an useEffect which is saying to place it inside the [] array. If you do so, it will later say to wrap getNewPins inside useCallback to prevent re-renders. Instead of that you can just place the whole function inside your useEffect like so (since it's a one time operation that's done initially) :-
function App() {
const [pins, setNewPins] = useState([])
const getImages = (term) => {
return unsplash.get ("https://api.unsplash.com/search" , {
params: {
query: term
}});
};
const onSearchSubmit = (term) => {
getImages(term).then((res) => {
let results = res.data.results;
let newPins = [
...results,
...pins,
]
newPins.sort(function(a,b){
return 0.5 - Math.random();
});
setNewPins(newPins);
});
};
useEffect(() => {
const getNewPins = () => {
let promises = [];
let pinData = [];
let pins = ['Istanbul','cats','sky','lake','nature']
pins.forEach((pinTerm) => {
promises.push (
getImages(pinTerm).then((res) => {
let results = res.data.results;
pinData = pinData.concat(results);
pinData.sort(function(a,b){
return 0.5 - Math.random();
});
})
);
});
Promise.all(promises).then(()=> {
setNewPins(pinData);
});
};
getNewPins();
}, []);
return (
<div className="app">
<Header onSubmit={onSearchSubmit}/>
<Mainboard pins={pins}/>
</div>
);
}
export default App;

Warning: Using UNSAFE_componentWillMount in strict mode is not recommended (upgrade to CRA 4.0.2)

I updated my React application from 16.3+ to React 17 while upgrading to crate-react-app#4.0.2. Everything works as expected, but I see the following in the console:
Warning: Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See react-unsafe-component-lifecycles for details.
* Move code with side effects to componentDidMount, and set initial state in the constructor.
Please update the following components: SideEffect(NullComponent)
My App.jsx file:
import React, { useRef, useEffect, useCallback, createRef } from 'react';
import { useDispatch, useSelector, batch } from 'react-redux';
import './App.scss';
import { CountryBox, Error, MasterBox, MetaTags, ModalContainer, ScreenLoader } from '../../components';
import { dataActions, settingsActions, statisticsActions, statisticsUpdatesActions } from '../../store/actions/actions';
import { engineService } from '../../services';
import { coreUtils } from '../../utils';
const App = (props) => {
const dispatch = useDispatch();
// Refs.
const elRefs = useRef([]);
// State variables.
const settingsList = useSelector((state) => state.settings.settingsList);
const loadingList = useSelector((state) => state.settings.loadingList);
const sourcesList = useSelector((state) => state.data.sourcesList);
const countriesList = useSelector((state) => state.data.countriesList);
const { isActive, isRefreshMode, viewType, isDisplayError, activeModalName,
activeModalValue, isReplaceModalMode, isActionLoader } = settingsList;
const { loadingPrecentage, isScreenLoaderComplete } = loadingList;
// Functions to update the state.
const onSetStateCurrentTime = (data) => dispatch(statisticsActions.setStateCurrentTime(data));
const onSetStateSettingsList = (listName, listValues) => dispatch(settingsActions.setStateSettingsList(listName, listValues));
const onSetStateStatisticsField = (fieldName, fieldValue) => dispatch(statisticsActions.setStateStatisticsField(fieldName, fieldValue));
const onSetStateStatisticsList = (statisticsList) => dispatch(statisticsActions.setStateStatisticsList(statisticsList));
const onSetStateStatisticsUpdatesSettingsList = (statisticsUpdatesSettingsList) => dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesSettingsList(statisticsUpdatesSettingsList));
const onSetStateDataCollection = (collectionName, collectionValue) => dispatch(dataActions.setStateDataCollection(collectionName, collectionValue));
const onSetStateInitiateSettings = (data) => {
const { settingsList, loadingList } = data;
batch(() => {
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
dispatch(settingsActions.setStateSettingsList('loadingList', loadingList));
});
};
const onSetStateInitiateSources = (data) => {
const { sourcesList, countriesList, countriesNameIdList, statisticsList, settingsList } = data;
batch(() => {
dispatch(dataActions.setStateDataCollection('sourcesList', sourcesList));
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(dataActions.setStateDataCollection('countriesNameIdList', countriesNameIdList));
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
});
};
const onSetStateUpdateRound = (data) => {
const { countriesList, statisticsList, updateStatisticsUpdatesListResults } = data;
const { statisticsUpdatesList, statisticsUpdatesSettingsList } = updateStatisticsUpdatesListResults;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
if (statisticsUpdatesList && statisticsUpdatesList.length > 0) {
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesList(statisticsUpdatesList));
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesSettingsList(statisticsUpdatesSettingsList));
}
});
};
const onSetStateActionUpdate = (data) => {
const { countriesList, settingsList } = data;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
});
};
const onSetStateActionRefresh = (data) => {
const { countriesList, settingsList, statisticsList, updateStatisticsUpdatesListResults } = data;
const { statisticsUpdatesList, statisticsUpdatesSettingsList } = updateStatisticsUpdatesListResults;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
if (statisticsUpdatesList && statisticsUpdatesList.length > 0) {
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesList(statisticsUpdatesList));
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesSettingsList(statisticsUpdatesSettingsList));
}
});
};
const onSetStateUpdateCountryVisibility = (data) => {
const { countriesList, countriesNameIdList, statisticsList, statisticsUpdatesList } = data;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(dataActions.setStateDataCollection('countriesNameIdList', countriesNameIdList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
if (statisticsUpdatesList && statisticsUpdatesList.length > 0) {
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesList(statisticsUpdatesList));
}
});
};
// Run the engine.
useEffect(() => {
engineService.runEngine({
mode: props.mode,
onSetStateCurrentTime: onSetStateCurrentTime,
onSetStateSettingsList: onSetStateSettingsList,
onSetStateStatisticsField: onSetStateStatisticsField,
onSetStateStatisticsList: onSetStateStatisticsList,
onSetStateStatisticsUpdatesSettingsList: onSetStateStatisticsUpdatesSettingsList,
onSetStateInitiateSettings: onSetStateInitiateSettings,
onSetStateInitiateSources: onSetStateInitiateSources,
onSetStateUpdateRound: onSetStateUpdateRound,
onSetStateDataCollection: onSetStateDataCollection,
onSetStateActionUpdate: onSetStateActionUpdate,
onSetStateActionRefresh: onSetStateActionRefresh,
onSetStateUpdateCountryVisibility: onSetStateUpdateCountryVisibility
});
return () => {
engineService.clearSources();
};
}, []);
// Set loader for each master action.
useEffect(() => {
engineService.updateActionLoader(false);
}, [countriesList]);
// After exit from any modal - Scroll back to the element's vertical position.
const scrollToCountry = useCallback((data) => {
const { action, value } = data;
if (action === 'modal' && !value && activeModalValue && !isReplaceModalMode && activeModalName !== 'country') {
setTimeout(() => {
const offsetTop = elRefs.current.find(c => c.current?.dataset?.countryId === activeModalValue).current.offsetTop;
if (offsetTop > window.innerHeight) {
window.scrollTo(0, offsetTop);
}
}, 10);
}
}, [elRefs, activeModalValue, isReplaceModalMode]);
// Update action on master modal click.
const handleActionClick = useCallback((e) => {
if (!isActionLoader) {
const data = {
action: coreUtils.getAttributeName(e, 'data-action'),
value: coreUtils.getAttributeName(e, 'name'),
id: coreUtils.getAttributeName(e, 'data-country-id')
};
scrollToCountry(data);
engineService.runMasterActionClick(data);
}
}, [elRefs, activeModalValue, isReplaceModalMode]);
// Update action on relevant modal change.
const handleModalActionChange = useCallback((e) => {
engineService.runModalActionUpdate({
modalName: coreUtils.getAttributeName(e, 'data-modal-name'),
action: coreUtils.getAttributeName(e, 'data-action'),
value: coreUtils.getValue(e)
});
}, []);
// Validate all OK to show the data and generate the countries.
const isInitiateComplete = !isDisplayError && countriesList && countriesList.length > 0 && loadingPrecentage === 100;
const renderCountries = useCallback(() => {
const countriesDOM = [];
const refsList = [];
for (let i = 0; i < countriesList.length; i++) {
const country = countriesList[i];
const ref = elRefs.current[i] || createRef();
refsList.push(ref);
countriesDOM.push(
(<CountryBox
key={country.id}
{...country} // React memo works only with separated properties.
isRefreshMode={isRefreshMode}
sourcesList={sourcesList}
onActionClick={handleActionClick}
ref={ref}
/>));
}
elRefs.current = refsList;
return countriesDOM;
}, [countriesList]);
return (
<div className="main">
{MetaTags}
{!isScreenLoaderComplete &&
<ScreenLoader
isActive={isActive}
loadingList={loadingList}
isDisplayError={isDisplayError}
/>
}
{isDisplayError &&
<Error
isDisplayError={isDisplayError}
/>
}
{activeModalName &&
<ModalContainer
onActionClick={handleActionClick}
onActionChange={handleModalActionChange}
/>
}
{isInitiateComplete &&
<div className="page">
<div className="main-container">
<div className={`container ${viewType} f32 f32-extra locations`}>
<MasterBox
onActionClick={handleActionClick}
/>
{renderCountries()}
</div>
</div>
</div>
}
</div>
);
};
export default App;
How can I fix this problem?
OK, I solved it.
The issue was with one of the components named MetaTags:
MetaTags.jsx
import React from 'react';
import { Helmet } from 'react-helmet';
import { timeUtils } from '../../../utils';
const MetaTags =
(<Helmet>
<title data-rh="true">World Covid 19 Data | Covid 19 World Data | {timeUtils.getTitleDate()}</title>
</Helmet>);
export default MetaTags;
The react-helmet package is outdated, and I needed to install 'react-helmet-async' instead, and change the code to:
initiate.jsx
app = (
<HelmetProvider>
<Suspense fallback={null}>
<Provider store={createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)))}>
<Helmet>
<title data-rh="true">Dynamic title {timeUtils.getTitleDate()}</title>
</Helmet>
<BrowserRouter>
{component}
</BrowserRouter>
</Provider>
</Suspense>
</HelmetProvider>
);
This solved my issue and the warning was gone.

How do I clear out an Image.getSize request when unmounting a React Native component?

I have a React Native component that makes an Image.getSize request for each image in the component. Then within the callback of the Image.getSize requests, I set some state for my component. That all works fine, but the problem is that it's possible for a user to transition away from the screen the component is used on before one or more Image.getSize requests respond, which then causes the "no-op memory leak" error to pop up because I'm trying to change state after the component has been unmounted.
So my question is: How can I stop the Image.getSize request from trying to modify state after the component is unmounted? Here's a simplified version of my component code. Thank you.
const imgWidth = 300; // Not actually static in the component, but doesn't matter.
const SomeComponent = (props) => {
const [arr, setArr] = useState(props.someData);
const setImgDimens = (arr) => {
arr.forEach((arrItem, i) => {
if (arrItem.imgPath) {
const uri = `/path/to/${arrItem.imgPath}`;
Image.getSize(uri, (width, height) => {
setArr((currArr) => {
const newWidth = imgWidth;
const ratio = newWidth / width;
const newHeight = ratio * height;
currArr = currArr.map((arrItem, idx) => {
if (idx === i) {
arrItem.width = newWidth;
arrItem.height = newHeight;
}
return arrItem;
});
return currArr;
});
});
}
});
};
useEffect(() => {
setImgDimens(arr);
return () => {
// Do I need to do something here?!
};
}, []);
return (
<FlatList
data={arr}
keyExtractor={(arrItem) => arrItem.id.toString()}
renderItem={({ item }) => {
return (
<View>
{ item.imgPath ?
<Image
source={{ uri: `/path/to/${arrItem.imgPath}` }}
/>
:
null
}
</View>
);
}}
/>
);
};
export default SomeComponent;
I had to implement something similar, I start by initialising a variable called isMounted.
It sets to true when the component mounts and false to when the component will unmount.
Before calling setImgDimens there's a check to see if the component is mounted. If not, it won't call the function and thus will not update state.
const SomeComponent = (props) => {
const isMounted = React.createRef(null);
useEffect(() => {
// Component has mounted
isMounted.current = true;
if(isMounted.current) {
setImgDimens(arr);
}
return () => {
// Component will unmount
isMounted.current = false;
}
}, []);
}
Edit: This is the answer that worked for me, but for what it's worth, I had to move the isMounted variable to outside the SomeComponent function for it to work. Also, you can just use a regular variable instead of createRef to create a reference, etc.
Basically, the following worked for me:
let isMounted;
const SomeComponent = (props) => {
const setImgDimens = (arr) => {
arr.forEach((arrItem, i) => {
if (arrItem.imgPath) {
const uri = `/path/to/${arrItem.imgPath}`;
Image.getSize(uri, (width, height) => {
if (isMounted) { // Added this check.
setArr((currArr) => {
const newWidth = imgWidth;
const ratio = newWidth / width;
const newHeight = ratio * height;
currArr = currArr.map((arrItem, idx) => {
if (idx === i) {
arrItem.width = newWidth;
arrItem.height = newHeight;
}
return arrItem;
});
return currArr;
});
}
});
}
});
};
useEffect(() => {
isMounted = true;
setImgDimens(arr);
return () => {
isMounted = false;
}
}, []);
};

Resources