cannot make react-leaflet to update markers dynamically: it gives TypeError: Cannot read property 'leafletElement' of undefined - reactjs

I have the table and I have the react-leaflet component drawn in a single tab (rc-tabs). They are not connected but Redux.
I have rows in the table with coordinates. When I click on the row, coordinates are passed into Tab component and then via props are moved to map and are drawn.
Well, they should be - when I pass the whole array of rows with coordinates - they are drawn just fine, but when I am passing single values - I meet some troubles.
I have testsData - where all rows are stored and, depending on what row is clicked, I find the index. When I pass to Map testData[0] - it is drawn fine. When I try to change the index with the help of redux - I got an error. I use approach with index and before that I used another - where I passed the whole row into the props - no luck.
const Map = (props) => {
return (
<LeafMap
preferCanvas={true}
zoom={zoom}
style={mapHeightStyle}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution="© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors"
/>
{props.tests.map(item => (
<Marker
className={item.id}
key={item.id}
position={item.coordinates[0], item.coordinates[1]}
}
>
</Marker>
))}
</FeatureGroup>
</LeafMap>
)
};
And here is my Tabs component (I cut some code!)
const Tabs = () => {
let clickedTestRow = useSelector(state => state.deviceTestsTable.rowClicked);
let testsData = useSelector(state => state.fetchTestsData.testsData);
let [markers, setMarkers] = useState([]);
let clickedTestRowIndex = 0;
if (Object.keys(clickedTestRow).length) {
clickedTestRowIndex = testsData.findIndex(x => x.id === clickedTestRow.id);
if (!markers.includes(clickedTestRow)) {
setMarkers(testsData[clickedTestRowIndex]]);
}
}
// initial value - showing the first row on map
useEffect(() => {
if (testsData.length > 0) {
setMarkers([testsData[clickedTestRowIndex]]);
}
}, [testsData]);
let props = {
tests: markers
};
const tabs = [
{key: 'Map', component: <Map {...props}/>, disabled: false},
];
What am I missing? Every time I have TypeError: Cannot read property 'leafletElement' of undefined

Got it! Sorry, this was really annoying - the problem was in my
mapRef.current.leafletElement.getBounds().contains(markerRef.current[item.id].leafletElement.getLatLng()))
where I referred to item, to see if it in within bound. For some reason leaflet didn't like it when I was update the state and data dynamically. I feel so sorry to have this error - I didn't type it above, because I was sure the problem is in another place...
Thanks, #rfestag and #kboul for comments!

Related

Get the ref of an element rendering by an array

I'm coding a tab navigation system with a sliding animation, the tabs are all visible, but only the selected tab is scrolled to. Problem is that, I need to get the ref of the current selected page, so I can set the overall height of the slide, because that page may be taller or shorter than other tabs.
import React, { MutableRefObject } from 'react';
import Props from './Props';
import styles from './Tabs.module.scss';
export default function Tabs(props: Props) {
const [currTab, setCurrTab] = React.useState(0);
const [tabsWidth, setTabsWidth] = React.useState(0);
const [currentTabHeight, setCurrentTabHeight] = React.useState(0);
const [currentTabElement, setCurrentTabElement] = React.useState<Element | null>(null);
const thisRef = React.useRef<HTMLDivElement>(null);
let currentTabRef = React.useRef<HTMLDivElement>(null);
let refList: MutableRefObject<HTMLDivElement>[] = [];
const calculateSizeData = () => {
if (thisRef.current && tabsWidth !== thisRef.current.offsetWidth) {
setTabsWidth(() => thisRef.current.clientWidth);
}
if (currentTabRef.current && currentTabHeight !== currentTabRef.current.offsetHeight) {
setCurrentTabHeight(() => currentTabRef.current.offsetHeight);
}
}
React.useEffect(() => {
calculateSizeData();
const resizeListener = new ResizeObserver(() => {
calculateSizeData();
});
resizeListener.observe(thisRef.current);
return () => {
resizeListener.disconnect();
}
}, []);
refList.length = 0;
return (
<div ref={thisRef} className={styles._}>
<div className={styles.tabs}>
{ props.tabs.map((tab, index) => {
return (
<button onClick={() => {
setCurrTab(index);
calculateSizeData();
}} className={currTab === index ? styles.tabsButtonActive : ''} key={`nav-${index}`}>
{ tab.label }
<svg>
<rect rx={2} width={'100%'} height={3} />
</svg>
</button>
)
}) }
</div>
<div style={{
height: currentTabHeight + 'px',
}} className={styles.content}>
<div style={{
right: `-${currTab * tabsWidth}px`,
}} className={styles.contentStream}>
{ [ ...props.tabs ].reverse().map((tab, index) => {
const ref = React.useRef<HTMLDivElement>(null);
refList.push(ref);
return (
<div ref={ref} style={{
width: tabsWidth + 'px',
}} key={`body-${index}`}>
{ tab.body }
</div>
);
}) }
</div>
</div>
</div>
);
}
This seems like a reasonable tab implementation for a beginner. It appears you're passing in content for the tabs via a prop named tabs and then keeping track of the active tab via useState() which is fair.
Without looking at the browser console, I believe that React doesn't like the way you are creating the array of refs. Reference semantics are pretty challenging, even for seasoned developers, so you shouldn't beat yourself up over this.
I found a good article that discusses how to keep track of refs to an array of elements, which I suggest you read.
Furthermore, I'll explain the differences between that article and your code. Your issues begin when you write let refList: MutableRefObject<HTMLDivElement>[] = []; According to the React hooks reference, ref objects created by React.useRef() are simply plain JavaScript objects that are persisted for the lifetime of the component. So what happens when we have an array of refs like you do here? Well actually, the contents of the array are irrelevant--it could be an array of strings for all we care. Because refList is not a ref object, it gets regenerated for every render.
What you want to do is write let refList = React.useRef([]), per the article, and then populate refList.current with refs to your child tabs as the article describes. Referring back to the React hooks reference, the object created by useRef() is a plain JavaScript object, and you can assign anything to current--not just DOM elements.
In summary, you want to create a ref of an array of refs, not an array of refs. Repeat that last sentence until it makes sense.

React scroll to element (ref current is null) problem

I have a problem I'm not able to solve. The app got a component where a do looping array and making multiple elements off it. Then I want to make buttons in another component that will scroll to a specific element. (something similar to liveuamap.com when you click on a circle).
I tried the below solution, but got "Uncaught TypeError: props.refs is undefined". I could not find any solution to fix it.
The second question: is there a better or different solution to make scrolling work?
In app component I creating refs and function for scrolling:
const refs = DUMMY_DATA.reduce((acc, value) => {
acc[value.id] = React.createRef();
return acc;
}, {});
const handleClick = (id) => {
console.log(refs);
refs[id].current.scrollIntoView({
behavior: "smooth",
block: "start",
});
};
The refs I send to the article component as a prop where I render elements with generated refs from the app component.
{props.data.map((article) => (
<ContentArticlesCard
key={article.id}
ref={props.refs[article.id]}
data={article}
onActiveArticle={props.onActiveArticle}
activeArticle={props.activeArticle}
/>
))}
The function is sent to another component as a prop where I create buttons from the same data with added function to scroll to a specific item in the article component.
{props.data.map((marker) => (
<Marker
position={[marker.location.lat, marker.location.lng]}
icon={
props.activeArticle === marker.id ? iconCircleActive : iconCircle
}
key={marker.id}
eventHandlers={{
click: () => {
props.onActiveArticle(marker.id);
// props.handleClick(marker.id);
},
}}
></Marker>
))}
Thanks for the answers.
Ok so i found the solution in library react-scroll with easy scroll implementation.

Why is my React Native component not re-rendering on state update?

I'm struggling with this React-Native component for a few days now. You should probably know that React-Native is kind of new to me so... sorry if the solution is obvious to you.
I'm using react-native-maps and I have several markers on my map. Each one of them has some data stored in my state and I want the callout to display a piece of this state on press.
Here are my states :
const [markersDetails, setMarkersDetails] = useState([]);
const [activeMarker, setActiveMarker] = useState({});
My activeMarker is updated by this function :
const markerSearch = (markerId) => {
let stockMarker = markersDetails.find((singleMarker) => {
return Number(singleMarker.idMarker) === markerId;
});
console.log("Stock ", stockMarker);
setActiveMarker(stockMarker);
console.log("State ", activeMarker);
};
And this function is called, inside my return, with the onPress of any marker :
<Marker
key={Number(marker.index)}
coordinate={{
latitude: Number(marker.latitude),
longitude: Number(marker.longitude),
}}
pinColor="blue"
onPress={() => {
markerSearch(Number(marker.index));
}}
>
{activeMarker !== {} && activeMarker.markerName && (
<Callout>
<View>
<Text>{activeMarker.markerName}</Text>
</View>
</Callout>
)}
</Marker>
But whenever I press on a marker, the callout opens immediatly while my state is not yet updated. So the text within the callout refers either to the previous marker or is empty (if it's the first marker I press on).
I've checked with console.log and my state is clearly updated but it takes a little bit more time. And I don't know why my callout is not re-rendering when this state is updating.
I've tried a ton of things to make this works but I can't figure this out...
Try doing something like that:
You can extract the section to a new component
Then inside this use the useEffect hook
export default function CalloutComponent({activeMarker}) {
const [markerName, setMarkerName] = useState('')
useEffect(() => {
setMarkerName(activeMarker?.markerName)
}, [activeMarker?.markerName])
if(!!markerName) return null
return (
<Callout>
<View>
<Text>{markerName}</Text>
</View>
</Callout>
)
}
And use this new component in your Main view
<Marker
...
>
<CalloutComponent activeMarker={activeMarker}/>
</Marker>

Why can't I manipulate the DOM directly with React refs in my project? [duplicate]

This question already has answers here:
How can I use multiple refs for an array of elements with hooks?
(17 answers)
Closed 1 year ago.
Since my pathfinding project has too many cells for me to animate it by changing state I was told to use ref. I went with the forward ref approach, which I assume works becase in devtools each cell appears with the forwardRef tag.
Even when I try to console.log each ref it points me to the correct cell in the grid. The problem is when I try to mutate a property like className/bgColor, program crashes and gives me a TypeError: Cannot add property className, object is not extensible.
const node = (props, ref) => {
return (
<div ref={ref}>
</div >
)
}
const Node = React.forwardRef(node);
export default Node
function Grid() {
// How I created the refs
const refCollection = useRef(matrix.map(rows => rows.map(nodes => React.createRef())));
let grid = matrix.map((rows, i) => rows.map((_, j) => <Node
ref={refCollection.current[i][j]}
></Node>))
function createMazeAndAnimate() {
const orderCarved = generateMaze(rows, cols, setWall, setStartNode,
setGoalNode, setTraversed, setCarvedOrder);
orderCarved.forEach(([y, x], i) => {
setTimeout(() => {
refCollection.current[y][x].className = 'traversed'; //PROGRAM CRASHES HERE
console.log(refCollection.current[y][x]); // IF I JUST LOG THIS LINE I CAN
// SEE EACH CELL REFERENCED
// CORRECTLY
}, i * 20);
})
}
return (
<>
<Toolbar
generateMaze={createMazeAndAnimate}
/>
<div
className='grid'
>
{grid.map((row, i) => <div key={i} className='board-row'>{row}</div>)}
</div>
</>
)
}
Thank You!
You're trying to modify refs directly. You don't mutate a ref object itself, you mutate whatever's in ref.current. So you want refCollection.current[y][x].current.
Putting a ref inside a ref is a code smell, as a ref itself is designed to be mutated, putting it inside something else you mutate seems extraneous. You might want to put your ref array in state instead.

Render Marker at certain interval

In context to my previous question,
I'm looking at rendering marker on map at certain intervals, like 10sec or 20 sec.
Below is the code ...
{
setInterval(() => {
this.tmcount += 1;
console.log(this.tmcount);
console.log(this.state.geoData[this.tmcount]);
return (
<Marker key={this.tmcount} position={[this.state.geoData[this.tmcount].lat, this.state.geoData[this.tmcount].lng]}>
<Popup>
{this.tmcount} Hi!!!
</Popup>
</Marker>
)
}, 1000)
}
I tried in above way but its not rendering..
Your question needs a lot of work. I have no idea of the context of where this setInterval is running, or where your actual map component is. That being said, you need to think of this differently. You want your map to update periodically, which means you need to have some state variable that keeps track of what's updating, and the map should respond in kind. Let's set up a component that has an array of markers in its state, and a map that renders what's in that array at any given time:
const MapComponent = () => {
const [markers, setMarkers] = useState([]) // start with nothing
return (
<MapContainer center={center} zoom={zoom}>
{markers.map((marker, index) => {
const key = {index} // bad idea here
const {lat, lng} = marker
return <Marker key ={} position={} />
})}
</MapContainer>
)
}
Initially, you'll have no markers, because your state variable is an empty array. But your component expects that if there are markers, they will have a .lat and .lng property on each entry in that array. Now, assuming you have some datasource which is an array of objects containing location info, you can set up a useEffect to progressively update your state variable with new entries from that dataset:
const [markers, setMarkers] = useState([])
useEffect(() => {
const interval = setInterval(() => {
// clone state variable
const updatedMarkers = [...markers]
// add next marker entry from source dataset
updatedMarkers.push(geoData[markers.length])
// setMarkers(updatedMarkers)
}, 1000) // 1 second interval
return () => { clearInterval(interval) } // clean up interval on unmount
}, [])
return ( ... )
So now, on component mount, a timer is set up to update the state every second with the next entry from your source data. The map will respond in kind and add the next marker every second. I haven't tested this, but hopefully its enough to give you an idea of what to do.

Resources