react-leaflet typeError when trying to render coordinates - reactjs

I'm working on a React project with Redux / thunk and I have this map from react-leaflet :
import { MapContainer, Marker, Popup, TileLayer, Circle } from "react-leaflet";
import { useSelector } from "react-redux";
function Maps() {
const user = useSelector((state: any) => state.userReducer.getUserById);
const coords: [number, number] = [user ? user.latitude : 0, user ? user.longitude : 0];
const fillBlueOptions = { fillColor: "blue" };
const zone = 10000;
return (
<MapContainer className="maps" center={coords} zoom={10} scrollWheelZoom={false}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Circle center={coords} pathOptions={fillBlueOptions} radius={zone} />
<Marker position={coords}>
<Popup>Adress</Popup>
</Marker>
</MapContainer>
);
}
export default Maps;
I get the coordinates of the user with an axios call :
export const getUserById = (id_user: number | string) => {
return (dispatch: any) => {
dispatch(isLoading(true));
return axios
.get(`users/getUserById/${id_user}`)
.then((res) => {
dispatch(isLoading(false));
dispatch({ type: GET_USER_BY_ID, payload: res.data });
})
.catch((err) => {
console.log(err);
dispatch(isLoading(false));
});
};
};
The dispatch is launched here when i click on this table :
<tbody>
{users.slice(0, 20).map((item: any, index: number) => (
<tr
className="redirectUser"
onClick={() => {
history.push("/Utilisateurs");
dispatch(getUserById(item.id));
}}
key={index}
>
<td>{item.id}</td>
<td>
{item.nom} {item.prenom}
<br></br>Tcheker
</td>
<td>{item.email}</td>
<td>18/10/1998</td>
<td>{item.tel}</td>
</tr>
))}
</tbody>
When I try to display the map component on a user who has the coords, i have this error (or the map just show blank screen) :
Uncaught TypeError: Cannot read property 'latitude' of undefined
Then if i go back in the history and re-click on the table, the map finally displays with no error.
When the user doesn't have any coords I get this error :
Uncaught TypeError: Cannot read property 'lng' of null
I understand that the component seems to render before getting the datas, but even with a ternary operator that gives a hard-coded value in case of an undefined answer, the component doesn't render neither.

I think this is an async issue. The very nature of your coords variable looks to be non-blocking and hence the render is performed before the ternary can be performed.
Since coords is just a variable (and not state driven) it never re-renders.
If you could set the state at the time coords is calculated and then refer you're leaflet map to the variables in state, I think you could get it working.
Good luck,
Phil

Related

State changed in context provider not saved

So I'm trying to centralize some alert-related logic in my app in a single .tsx file, that needs to be available in many components (specfically, an "add alert" fuction that will be called from many components). To this end I am trying to use react context to make the alert logic available, with the state (an array of active alerts) stored in App.tsx.
Alerts.tsx
export interface AlertContext {
alerts: Array<AppAlert>,
addAlert: (msg: React.ReactNode, style: string, callback?: (id: string) => {}) => void,
clearAlert: (id: string) => void
}
[...]
export function AlertsProvider(props: AlertsProps) {
function clearAlert(id: string){
let timeout = props.currentAlerts.find(t => t.id === id)?.timeout;
if(timeout){
clearTimeout(timeout);
}
let newCurrent = props.currentAlerts.filter(t => t.id != id);
props.setCurrentAlerts(newCurrent);
}
function addAlert(msg: JSX.Element, style: string, callback: (id: string) => {}) {
console.log("add alert triggered");
let id = uuidv4();
let newTimeout = setTimeout(clearAlert, timeoutMilliseconds, id);
let newAlert = {
id: id,
msg: msg,
style: style,
callback: callback,
timeout: newTimeout
} as AppAlert;
let test = [...props.currentAlerts, newAlert];
console.log(test);
props.setCurrentAlerts(test);
console.log("current alerts", props.currentAlerts);
}
let test = {
alerts: props.currentAlerts,
addAlert: addAlert,
clearAlert: clearAlert
} as AlertContext;
return (<AlertsContext.Provider value={test}>
{ props.children }
</AlertsContext.Provider>);
}
App.tsx
function App(props: AppProps){
[...]
const [currentAlerts, setCurrentAlerts] = useState<Array<AppAlert>>([]);
[...]
const alertsContext = useContext(AlertsContext);
console.log("render app", alertsContext.alerts);
return (
<AlertsProvider currentAlerts={currentAlerts} setCurrentAlerts={setCurrentAlerts}>
<div className={ "app-container " + (error !== undefined ? "err" : "") } >
{ selectedMode === "Current" &&
<CurrentItems {...currentItemsProps} />
}
{ selectedMode === "History" &&
<History {...historyProps } />
}
{ selectedMode === "Configure" &&
<Configure {...globalProps} />
}
</div>
<div className="footer-container">
{
alertsContext.alerts.map(a => (
<Alert variant={a.style} dismissible transition={false} onClose={a.callback}>
{a.msg}
</Alert>
))
}
{/*<Alert variant="danger" dismissible transition={false}
show={ error !== undefined }
onClose={ dismissErrorAlert }>
<span>{ error?.msg }</span>
</Alert>*/}
</div>
</AlertsProvider>
);
}
export default App;
I'm calling alertsContext.addAlert in only one place in CurrentItems.tsx so far. I've also added in some console statements for easier debugging. The output in the console is as follows:
render app Array [] App.tsx:116
XHRGEThttp://localhost:49153/currentitems?view=Error [HTTP/1.1 500 Internal Server Error 1ms]
Error 500 fetching current items for view Error: Internal Server Error CurrentItems.tsx:94
add alert triggered Alerts.tsx:42
Array [ {…}, {…} ] Alerts.tsx:53
current alerts Array [ {…} ] Alerts.tsx:55
render app Array []
So I can see that by the end of the addAlert function the currentAlerts property appears to have been updated, but then subsequent console statement in the App.tsx shows it as empty. I'm relatively new to React, so I'm probably having some misunderstanding of how state is meant to be used / function, but I've been poking at this on and off for most of a day with no success, so I'm hoping someone can set me straight.
const alertsContext = useContext(AlertsContext);
This line in App is going to look for a provider higher up the component tree. There's a provider inside of App, but that doesn't matter. Since there's no provider higher in the component tree, App is getting the default value, which never changes.
You will either need to invert the order of your components, so the provider is higher than the component that's trying to map over the value, or since the state variable is already in App you could just use that directly and delete the call to useContext:
function App(props: AppProps){
[...]
const [currentAlerts, setCurrentAlerts] = useState<Array<AppAlert>>([]);
[...]
// Delete this line
// const alertsContext = useContext(AlertsContext);
console.log("render app", currentAlerts);
[...]
{
currentAlerts.map(a => (
<Alert variant={a.style} dismissible transition={false} onClose={a.callback}>
{a.msg}
</Alert>
))
}
}

Rendering a MapContainer on several subpages results in error

I'm having some issues with MapContainer from react-leaflet throwing errors at me in my app.
I have a tabel of events, that links to a page with event details, this details page implements a map with the following component:
export function MapWithPoints(props: Props) {
const { points } = props
return (
<MapContainer scrollWheelZoom={false} style={{ height: 300, width: '100vw' }}>
<InnerMap points={points} />
</MapContainer>
)
}
InnerMap is defined as:
function InnerMap(props: Props) {
const { points } = props
const leafletMap = useMap()
React.useEffect(() => {
if (leafletMap && points.length) {
const pointArray: L.LatLngTuple[] = points.map((item) => [
item.lat,
item.lng,
])
const bounds = L.latLngBounds(pointArray)
leafletMap.fitBounds(bounds)
}
}, [points, leafletMap])
return (
<React.Fragment>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{points
.filter((poi) => poi.lat && poi.lng)
.map((poi) => (
<Marker
position={[poi.lat, poi.lng]}
key={`${poi.id}_${poi.lat}_${poi.lng}`}
icon={poi.icon ? poi.icon : DefaultIcon}
>
{poi.description ? <Popup>{poi.description}</Popup> : null}
</Marker>
))}
</React.Fragment>
)
}
The map works when I go to the first (random) entry in my list of events, the event page loads like it should, but if I go back and then choose another event page, MapContainer throws an error:
Uncaught Error: Map container is already initialized.
at NewClass._initContainer (Map.js:1092:1)
at NewClass.initialize (Map.js:136:1)
at new NewClass (Class.js:24:1)
at MapContainer.js:31:1
at commitAttachRef (react-dom.development.js:23645:1)
at safelyAttachRef (react-dom.development.js:22891:1)
at reappearLayoutEffectsOnFiber (react-dom.development.js:23545:1)
at reappearLayoutEffects_complete (react-dom.development.js:24838:1)
at reappearLayoutEffects_begin (react-dom.development.js:24826:1)
at commitLayoutEffects_begin (react-dom.development.js:24649:1)
I'm using react 18, and react-leaflet 4.0.0
Is there something I have missed in the setup?
There is a bug in 4.0.0, but even when using the latest version, I still get the error now and then. A workaround is to have a unique key on Map Container, for example timestamp. But this result in a memory leak, as each time a map is loaded it consumes more memory

setState doesn't re-render react functional component

I am getting user's location and setting it as the new state at "onSuccess" function,
the component doesn't re-render.
After checking a lot i have seen that react doesn't see it as a change of state because it is an array in that case and it doesn't pass react's "equality" check as a state that was changed.
With that, nothing that i have tried has worked. any ideas?
import { useEffect, useState } from "react";
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
export default function Map() {
const [location, setLocation] = useState([52.234, 13.413]);
const onSuccess = (position) => {
let userLocation = [position.coords.latitude, position.coords.longitude];
setLocation([...userLocation]);
};
useEffect(() => {
if (!("geolocation" in navigator)) {
alert("no Geolocation available");
}
navigator.geolocation.getCurrentPosition(onSuccess);
}, []);
console.log(location);
return (
<>
<MapContainer
className="leaflet-map"
center={location}
zoom={11}
scrollWheelZoom={false}
>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={[51.505, -0.09]}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</MapContainer>
</>
);
}
It looks like MapContainer does not recenter after mounting even if the center prop is changed as mentioned in the docs:
https://react-leaflet.js.org/docs/api-map
Except for its children, MapContainer props are immutable: changing them after they have been set a first time will have no effect on the Map instance or its container.
You could force replacing the MapContainer by passing a key prop that you change whenever location changes, for example:
<MapContainer
key={`${location[0]}-${location[1]}`}
Or investigating other options in react-leaflet such as useMap to get access to the Leaflet Map instance and calling map.setView(...) https://leafletjs.com/reference-1.7.1.html#map-setview
Are you able to confirm that onSuccess is called at all? It may be that getCurrentPosition is running into an error, so calling it with two arguments would be good:
navigator.geolocation.getCurrentPosition(onSuccess, onError);
You should also include onSuccess in the useEffect dependencies.
useEffect(() => {
if (!("geolocation" in navigator)) {
alert("no Geolocation available");
}
navigator.geolocation.getCurrentPosition(onSuccess);
}, [onSuccess]);
And to prevent multiple calls to getCurrentPosition due to onSuccess changing, you should also useCallback with the dependency on setLocation:
const onSuccess = useCallback((position) => {
let userLocation = [position.coords.latitude, position.coords.longitude];
setLocation([...userLocation]);
}, [setLocation]);

ReactJS sending ref to global useContext state (Konva)

I am using useContext as a global state solution. I have a Store.jsx which contains my state, and a reducer.jsx which reduces. I am using Konva to create some shapes on an HTML5 Canvas. My goal is when I click on a shape I want to update my global state with a reference to what is active, and when I click again, to clear the reference.
My Full Code can be found here:
https://codesandbox.io/s/staging-platform-2li83?file=/src/App.jsx
Problem:
The problem is when I update the global state via the onClick event of a shape, its says that the reference is 'null', but when I console.log the reference in the onClick I can see the correct reference.
I think I am missing an important point to how useRef works.
This is how the flow appears in my head when I think about this:
I create a canvas, and I map an array of rectangle properties. This creates 4 rectangles. I use a wrapper component that returns a rectangle.
{rectArray.map((rectangle, index) => {
return (
<RectWrapper key={index} rectangle={rectangle} index={index} />
);
})}
Inside the RectWrapper, I create a reference, pass it to the ref prop of the Rect. In the onclick function, when I console log 'shapeRef' I see the refence ONLY when dispatch is commented out. If I uncomment dispatch then it shows as null, and if I console log the state, the reference is always null.
const RectWrapper = ({ rectangle, index }) => {
const shapeRef = React.useRef();
return (
<Rect
x={rectangle.x + index * 100}
y={5}
width={50}
height={50}
fill="red"
ref={shapeRef}
onClick={() => {
console.log("ShapeRef: ");
console.log(shapeRef); // This correctly identifies the rect only when dispatch is uncommented
dispatch({
type: "active_image",
payload: {
index: index,
reference: shapeRef
}
});
}}
/>
);
};
perhaps I am going about this to wrong way with hooks. I am just trying to keep a global state of whats been clicked on because components in another file would rely on this state.
The problem is happening because you are creating RectWrapper component as a functional component within your App component causing a new reference of the component to be created again and again and thus the reference is lost
Move your RectWrapper into a separate component declared outside of App component and pass on dispatch as a prop to it
import React, { useEffect, useContext, useState, Component } from "react";
import { Stage, Layer, Rect, Transformer } from "react-konva";
import { Context } from "./Store.jsx";
import "./styles.css";
const RectWrapper = ({ rectangle, index, dispatch }) => {
const shapeRef = React.useRef();
return (
<Rect
x={rectangle.x + index * 100}
y={5}
width={50}
height={50}
fill="red"
ref={shapeRef}
onClick={() => {
console.log("ShapeRef: ");
console.log(shapeRef);
dispatch({
type: "active_image",
payload: {
index: index,
reference: shapeRef
}
});
}}
/>
);
};
export default function App() {
const [state, dispatch] = useContext(Context);
console.log("Global State:");
console.log(state);
const rectArray = [
{ x: 10, y: 10 },
{ x: 10, y: 10 },
{ x: 10, y: 10 },
{ x: 10, y: 10 }
];
return (
<div className="App">
<Stage height={500} width={500}>
<Layer>
{rectArray.map((rectangle, index) => {
return (
<RectWrapper
dispatch={dispatch}
key={index}
rectangle={rectangle}
index={index}
/>
);
})}
</Layer>
</Stage>
</div>
);
}
Working demo
I don't think you need to create a ref in RectWrapper, because onClick has one event parameter. And the ref of the element that was clicked can be found in the event:
onClick={(e) => {
const thisRef = e.target;
console.log(thisRef );
...
Here is a working version without useRef: https://codesandbox.io/s/peaceful-brook-je8qo

Markers not working with google-map-react

I'm working with the 'google-map-react' library and I have tried all but the markers are not showing up.
I pass the coords to the marker in many ways but none worked.
Here's my code & repository:
https://github.com/jorginyu/ubica
import React, { Component } from 'react';
import GoogleMapReact from 'google-map-react';
const API_KEY = 'WTFULOOKINAT';
const contacts = [
{ name: 'Spiderman', lat: 41.529616, lng: 2.434130 },
{ name: 'Iron Man', lat: 41.528103, lng: 2.433834 },
{ name: 'Hulk', lat: 41.530192, lng: 2.422994 }
];
const MarkersC = (text ) => <div className="contact">{text}</div>;
export default class MapComponent extends Component {
constructor(props) {
super(props);
this.state = {
center: {
lat: 41.528452,
lng: 2.434195
},
zoom: 18
}
}
render() {
return (
// Important! Always set the container height explicitly
<div className="mt-5" style={{ height: '80vh', width: '100%' }}>
<GoogleMapReact
bootstrapURLKeys={{ key: API_KEY }}
defaultCenter={this.state.center}
defaultZoom={this.state.zoom}
>
{contacts.map((contact,i) => {
<MarkersC position={{lat: contact.lat, lng: contact.lng}} text={contact.name} key={i} />
})}
</GoogleMapReact>
</div>
);
}
}
What can I do? Thanks for your time :)
There is a formatting problem. I deleted the spaces:
THEN:
{
contacts.map((contact, i) =>
<MarkersC lat={contact.lat} lng={contact.lng} text={contact.name} key={i} />
)
}
NOW:
{
contacts.map((contact, i) => <MarkersC lat={contact.lat} lng={contact.lng} text={contact.name} key={i} /> )
}
If you open the browser's console, you will see an error. The problem is with your MarkerC component and how you try to get the text prop.
The parameter of the component is an object with all properties that are passed to it.
You do not destructure it to get the text you simply use the whole parameter and try to display it.
So you need to propertly destructure it as const MarkersC = ( {text} ) => ..
Instead of
const MarkersC = ( text ) => <div className="contact">{text}</div>;
it should be
const MarkersC = ( {text} ) => <div className="contact">{text}</div>;
Update
Just noticed, the google-map-react expect to find lat and lng properties on the marker. You have wrapped them inside a position property so they cannot be found.
So your usage should be
either
<MarkersC lat={contact.lat} lng={contact.lng} text={contact.name} key={i} />
or spread the whole contact object that holds those properties
<MarkersC {...contact} key={i} />
so that the lat,lng and text are all direct properties of the MarkersC component.
Declaring the object type and referencing directly the value of 'text' on the initial 'const' worked for me:
const AnyReactComponent = (text:any) => <div>{text.text}</div>;
In your question, you have provided invalid GoogleMap key so it is showing error in console Google Maps JavaScript API error: InvalidKeyMapError.
So provide a valid GoogleMap key or blank(const API_KEY = '') for development use only.
In my case with blank API_KEY, it's working fine.
(Your git repo code is different than code you have posted here in StackOverflow.)

Resources