how to ensure data isn't undefined before initial render? - reactjs

I have a map component which I have been using to plot polyline data on a react leaflet map.
I want the initial center of the leaflet map to be the first position in my array of latitude and longitude data. I created a function to set values based on two different conditions these work correctly the values get set as can be seen through the logs to the console.
But when my component is initially rendered it causes an error because it says that initialPositionLat and initialPositionLong are undefined.
I tried using useEffect to set the values but I am unsure how to ensure initialPositionLat and initialPositionLong are set from the function before the component is rendered. Does anyone know where I am going wrong?
The map component:
function Mapp(props) {
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
setInitPosition(props);
setIsLoading(false);
},[]);
let initialPositionLat;
let initialPositionLong;
function setInitPosition(props) {
if(!Array.isArray(props.activityData)) {
console.log("first if positions = ", props.activityData.positions[0][1])
initialPositionLat = props.activityData.positions[0][0];
initialPositionLong = props.activityData.positions[0][1];
}
else {
console.log("2nd if positons = ", props.activityData.polylines.positions)
initialPositionLat = props.activityData.poylines.positions[0][0];
initialPositionLong = props.activityData.poylines.positions[0][1]
}
//console.log("initial position = ", [initialPositionLat, initialPositionLong]);
}
return !isLoading ? (
<MapContainer center={[initialPositionLat, initialPositionLong]} zoom={15} scrollWheelZoom={false}>
<TileLayer attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{!Array.isArray(props.activityData) && <Polyline positions={props.activityData.positions} >
</Polyline>
}
{Array.isArray(props.activityData.polylines) && props.activityData.polylines.length > 1 &&
props.activityData.polylines.map((activity, idx) => (
<Polyline key={idx} positions={activity.positions}
</Polyline>
))}
</MapContainer>
) : (
<div>
<p>Loading...</p>
</div>
)
}
export default Mapp;

You can do like this,
<MapContainer center={getInitPosition()} zoom={15} scrollWheelZoom={false}>
and change the function,
function getInitPosition() {
if(!Array.isArray(props.activityData)) {
console.log("first if positions = ", props.activityData.positions[0][1])
return [props.activityData.positions[0][0],props.activityData.positions[0][1]];
}
else {
console.log("2nd if positons = ", props.activityData.polylines.positions)
return [props.activityData.poylines.positions[0][0],props.activityData.poylines.positions[0][1]];
}
//console.log("initial position = ", [initialPositionLat, initialPositionLong]);
}

Related

polylinedacorator with react leaflet 4

I am trying to include arrows to the Polyline in react-leaft. For that I am using polylinedecorator plugin. There is a similar post on this platform. However, it uses withLeaflet module which is not supported in react-leaflet 4.0. How can I make it run without using 'withLeaflet'.
I have tried to implement it with the hooks. However, it does not work and need some assistance, how can I make it run.
export default function App(): JSX.Element {
const polylineRef = useRef<any>(null);
const arrow = [
{
offset: "100%",
repeat: 0,
symbol: L.Symbol.arrowHead({
pixelSize: 15,
polygon: false,
pathOptions: { stroke: true }
})
}];
useEffect(()=>{
L.polylineDecorator(polylineRef.current,{
patterns: arrow
})
}, [polylineRef]);
return (
<MapContainer center={center} zoom={13} scrollWheelZoom={true} style={{height: 'calc(100% - 30px)'}}>
<TileLayer
attribution='© OpenStreetMap contributors'
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/>
{currentData?.movingActors.map(line =>(<Polyline key={line.id}
positions={[line.startLocation, line.endLocation] } ref={polylineRef}
color={modeColor(line.mode)}
/>
))}
</MapContainer>
</>);}
CHANGES MADE TO THE ACCEPTED ANSWER TO MAKE IT RUN
function PolylineDecorator({ patterns, polyline,color }) {
const map = useMap();
useEffect(() => {
if (!map) return;
L.polyline(polyline, {color}).addTo(map); // added color property
L.polylineDecorator(polyline, {
patterns,
}).addTo(map);
}, [map]);
return null;
}
{currentData?.movingActors.map(line =>(<PolylineDecorator key={line.id} patterns ={arrow} polyline={position} color = {modeColor(line.mode)} />) ) } //here I used color parameters to dynamically add colors
What you need is a custom react functional component that returns null and has a useEffect with the code to initialize the plugin:
function PolylineDecorator({ patterns, polyline }) {
const map = useMap();
useEffect(() => {
if (!map) return;
L.polyline(polyline).addTo(map);
L.polylineDecorator(polyline, {
patterns
}).addTo(map);
}, [map]);
return null;
}
and then use it like:
<MapContainer...>
<TileLayer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png" />
<PolylineDecorator patterns={arrow} polyline={polyline} />
</MapContainer>
Demo

Show vector layers through Leaflet.MagnifyingGlass on react-leaflet 4

I am implementing the 'Leaflet.MagnifyingGlass' on my typescript react-leaflet v4 project. I am able to show the magnifying glass on the tile layer however, I am not sure, how to implement it on the already overlaid vector layer.
function Lense (){
const lenseLayer = useMap();
const magnifiedTiles = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png")
useEffect(() => {
L.magnifyingGlass({
layers: [magnifiedTiles]
//layers: [PolylineDecorator]
}).addTo(lenseLayer)
}, [lenseLayer]);
return <></>;
}
export default function App(): JSX.Element {
<MapContainer center={center} zoom={13} scrollWheelZoom={true} style={{height: 'calc(100% - 30px)'}}>
<TileLayer
attribution='© OpenStreetMap contributors'
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' />
{currentData?.movingActors.map(line =>(<PolylineDecorator key={line.id}
polyline={positions} color={modeColor(line.mode)}/>))}
<Lense />
</MapContainer>
</>
);
}
As one can see, that at the moment, magnifying glass only is laid to tile object. I need to lay it on top of the polylindedecorator object. The function is defined as
function PolylineDecorator({ polyline,color }) {
const map = useMap();
useEffect(() => {
if (!map) return;
var x = L.polyline(polyline, {color}).arrowheads({ size: '5%' });
x.addTo(map);
}, [map]);
return null;
}
Any suggestions, how can I achieve that.
PS I have already tried react-leaflet-magnifying-glass, but it is not compatible with the latest react-leaflet version.

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

Dynamically add circles to react leaflet with blockchain

I have read this post which partly answers my question, but my problem is the infinite loop that the provider.on method creates with setData(_data). I simply want to update the circle information to be rendered from my local blockchain, but the setData(_data) call creates an infinite loop.
I have tried using a global variable instead of using useState, which solves the infinite loop, but this updated value cannot be seen in other parts of the code.
function App() {
const initialPos = [55, 12];
const zoomLv = 13;
const [data, setData] = useState([])
const greeterAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3"
const provider = new ethers.providers.JsonRpcProvider("http://localhost:8545");
const contract = new ethers.Contract(greeterAddress, Greeter.abi, provider)
provider.on("block", async (blockNumber) => {
const _data = await contract.getCircle().split(", ") //[lat, lng, radius]
setData(_data)
});
function RenderCircle() {
if (data.length > 1) {
return <Circle center={[parseFloat(data[0]), parseFloat(data[1])]} radius={parseFloat(data[2])} />
}
return null
}
return (
<>
<MapContainer center={initialPos} zoom={zoomLv} id='map'>
<TileLayer
url="https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png"
attribution='© OpenStreetMap contributors'
maxZoom={20}
/>
<RenderCircle/>
</MapContainer>
</>
);
}
To solve it, I made it a react component class, and used its setState() instead of using useState(). This avoided the infinite loop.

React Native maps re-render

I have a progress bar(react-native-elements) that is animated using useEffect(). This component has MapView(react-native-maps) and it's re-rendered on every tick thus I can't move the map.
How can I still have that progress bar without re-rendering the map?
Home
const Home = ({ navigation }) => {
...some code above...
useEffect(() => {
//setProgressValue(0);
var timer;
if (progressValue != 0) {
timer = setInterval(() => {
if (progressValue <= 1) {
setProgressValue(progressValue + 0.040);
}
else {
setProgressValue(0);
setTimeOver(true);
}
}, 1000);
return () => {
clearInterval(timer);
}
}
}, [progressValue]);
... code below ...
return (
...
<MainMap notification={notification} />
...
)
MainMap component
<View style={StyleSheet.absoluteFillObject}>
<MapView
ref={mapRef}
provider={PROVIDER_GOOGLE}
region={geo.location}
style={[StyleSheet.absoluteFillObject]}
loadingEnabled={true}
minZoomLevel={16}
maxZoomLevel={20}
>
{/* Current users location */}
{geo.location &&
<Marker identifier={'me'} pinColor={"red"} key={(Date.now()-1).toString()} title={"Это я"} coordinate={geo.location} />
}
{/* Location where help is needed */}
{notification &&
<Marker identifier={'target'} pinColor={COLORS.primary} key={Date.now.toString()} title={"Помогите!"} coordinate={notification.request.content.data.loc} />
}
</MapView>
</View>
I used the useMemo hook for the map and passed params that will trigger its rerender making sure that other state changes are not triggering it.

Resources