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
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
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;
};
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;
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.
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;
}
}, []);
};