React-mapbox gl accessing Map's "zoom" in a stateless functional component - javascript

I need to be able to access the current "Zoom" level of the map for dynamically rendering content. Is there a way I can do this in a stateless functional component? I have seen this question, which only applies to a class-component.
I've looked at current issue and tried giving the map to props, neither of those solutions seem to work.
const Map = ReactMapboxGl({
accessToken: AT
});
const MapPortion = (props) => {
const [mapOptions, setMapOptions] = useState({zoom: [4]});
const someZoomFunction = () => {
// do stuff to setMapOptions({})
}
return (
<Map
zoom={zoom}
containerStyle={{
height: "90%",
width: "100%"
}}
/>
)
}
I'd like to not have to rewrite everything I have just to access zoom. Thanks in advance!

Resolved this. See this issue: https://github.com/alex3165/react-mapbox-gl/issues/763

Related

Automatic scroll with ScrollView in React native

I have a list of comments in my app and I would like that when the user accesses the notification, the app would automatically scroll to the comment.
My code is like this:
comments.tsx
const scrollViewRef = useRef(null)
const commentRef = useRef(null)
useEffect(() => {
if(scrollViewRef.current && commentRef.current)
commentRef.current?.measureLayout(
scrollViewRef.current,
(x, y) => {
scrollViewRef.current.scrollTo({x: 0, y, animated: true})
}
)
}, [scrollViewRef.current, commentRef.current])
<ScrollView ref={scrollViewRef}>
...
<Comments>
{comments.map(comment => {
<Comment ref={commentId === commentIdNotification ? commentRef : null} />
)}
</Comments>
</ScrollView>
The problem is that the measureLayout value is usually wrong and doesn't go to the comment. I believe it is a problem with the rendering since the component makes several requests to APIs and takes a while to finish rendering.
How can I solve this problem?
Is there a reason for using a ScrollView with a map-function instead of a FlatList with the Comment-component as renderItem? You will probably get better control and performance with a FlatList.
In that case, you can simply call FlatLists scrollToIndex().
See documentation here: https://reactnative.dev/docs/flatlist#scrolltoindex

How can I set 2 values for one variable, and the component will render with one value, depending of some prop input

I'm new here, So if my question is not good, Please let me know so that I can edit.
I'm using ReactJS + Material UI. I have a component, but I want this component to be rendered with different properties depending on the props, like this:
In the page where I want render the component:
<AdBanner vertical={true} />
Inside my AdBanner component I have:
export default function AdBanner(props) {
const [adWidth, setAdWidth] = useState("100%");
const [adHeight, setAdHeight] = useState("90px");
const [adSpacing, setAdSpacing] = useState(2);
const [adDirection, setAdDirection] = useState("row");
useEffect(() => {
if (props.vertical) {
console.log("ola");
setAdWidth("320px");
setAdHeight("480px");
setAdSpacing(5);
setAdDirection("column");
}
}, [props.vertical]);
return (
<>
<Paper
variant="outlined"
sx={{ width: { xs: "100%", md: adWidth }, overflow: "hidden" }}
>
...
My goal is, when I don't specify a value for the "vertical" property my component has certain characteristics (like height, width, ... ). But in some parts of my application I want a set of others values ​​for the same property.
I was getting some errors, with the help of #guilfer I was able to eliminate these errors.
Can anyone tell me if what I did is correct? Thanks.
Here the full code:
https://github.com/brunovjk/saude-vapor
Thank you.
There are some things on your code that you can't actualy do in javascript.
When a variable is defined using the "var", it has global scope.
Here is an explanation about the differences: https://www.freecodecamp.org/news/var-let-and-const-whats-the-difference/
After that, there are some other things that we don't actualy do in React, usualy in your case, there would be two different classes with the style you want, then, for aplying the style you could do something similar to this:
import style from "style.module.css"
export default function AdBanner(props) {
return <div className={props.vertical? style.firstClass : style.secondClass}>banner content<div>
}
.firstClass {
...one css styles
}
.secondClass {
...another css style
}

Coordinates array from PolylineMeasure plugin (react leaflet)

I have 3 files:
1.
PolylineMeasure.jsx
import { MapControl, withLeaflet } from "react-leaflet";
import * as L from "leaflet";
class PolylineMeasure extends MapControl {
createLeafletElement() {
return L.control.polylineMeasure({
position: "topleft",
unit: "metres",
showBearings: true,
clearMeasurementsOnStop: false,
showClearControl: true,
showUnitControl: true,
});
}
componentDidMount() {
const { map } = this.props.leaflet;
const polylineMeasure = this.leafletElement;
polylineMeasure.addTo(map);
}
}
export default withLeaflet(PolylineMeasure);
Map.jsx
import { Map, TileLayer } from "react-leaflet";
import PolylineMeasure from "./PolylineMeasure";
import "leaflet/dist/leaflet.css";
import "leaflet/dist/leaflet.css";
import "leaflet.polylinemeasure/Leaflet.PolylineMeasure.css";
import "leaflet.polylinemeasure/Leaflet.PolylineMeasure";
const Leaflet = () => {
return (
<>
<Map
center={[52.11, 19.21]}
zoom={6}
scrollWheelZoom={true}
style={{ height: 600, width: "50%" }}
>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<PolylineMeasure />
</Map>
</>
);
};
export default Leaflet;
I'm using nextjs so I had to import without SSR.
home.js
import dynamic from "next/dynamic";
function HomePage() {
const Map = dynamic(() => import("../components/Map"), {
loading: () => <p>A map is loading</p>,
ssr: false,
});
return <Map />;
}
export default HomePage;
https://github.com/ppete2/Leaflet.PolylineMeasure
Using demos in link above, I was able to log an array of coorfinates like this:
{ ... }
polylineMeasure.addTo(map);
function debugevent() {
polylineMeasure._arrPolylines[0].arrowMarkers.map((el) => {
console.log(el._latlng);
});
}
map.on("polylinemeasure:toggle", debugevent);
How can I access these coordinates in nextjs (home.js file)?
How to render PolylineMeasure (Map.jsx file) already with coordinates by passing down an array as props?
So this is about 2 things: lifting up state, and capturing Leaflet.Polyline's internal events.
First, let's keep track of a state variable in Home.js, and pass its setter down into the map component:
function HomePage() {
const [pointarray, setPointarray] = useState()
const Map = dynamic(() => import("../components/Map"), {...})
return <Map setPointarray={setPointarray} />;
}
Now in Map, we need to get a reference to the underlying leaflet map so that we can attach some event handlers. You're using createLeafletElement and withLeaflet, so I assume you're using reat-leaflet version 2. (I recommend updating to v3 when you can).
const Leaflet = ({ setPointarray }) => {
const mapRef = React.useRef()
useEffect(() => {
if (mapRef && mapRef.current){
mapRef.current.leafletElement.on(
'polylinemeasure:finish',
currentLine => setPointarray(currentLine.getLatLngs())
)
}
}, [mapRef])
return (
<>
<Map
ref={mapRef}
...
>
<TileLayer ... />
<PolylineMeasure />
</Map>
</>
);
};
What happens here is that a ref is attached to your Map component, which references the underlying leaflet L.map instance. When that ref is ready, the code inside the useEffect if statement runs. It gets the map instance from mapRef.current.leafletElement, and attaches an event handler based on Leaflet.PolylineMeasure's events, specifically the event of when a drawing is complete. When that happens, it saves the drawn line to the state variable, which lives in the Home component.
There are a lot of variations on this, it just depends on what you're trying to do exactly. As far as feeding preexisting polyline coordinates down to PolylineMeasurer as props, I couldn't find any examples of that even with the vanilla leaflet PolylineMeasurer. I found a comment from the plugin author saying that "restoring of drawed measurements is not possible", which is essentially what we're talking about doing by passing props down to that component. I'm sure it can be done by digging into the source code and programmatically drawing a polyline, but I've run out of time, I'll try to revisit that later.
react-leaflet version 3 answer
As per request, here's how to do this with react-leaflet v3, while initializing the polylinemeasurer with data passed down as props.
Create custom react-leaflet v3 control
Creating custom components with react-leaflet is easier than ever. Take a look at createcontrolcomponent. If you're not used to reading these docs, it boils down to this: to create a custom control component, you need to make a function that returns the leaflet instance of the control you want to make. You feed that function to createcontrolcomponent, and that's it:
import { createControlComponent } from "#react-leaflet/core";
const createPolylineMeasurer = (props) => {
return L.control.polylineMeasure({ ...props });
};
const PolylineMeasurer = createControlComponent(createPolylineMeasurer);
export default PolylineMeasurer;
Altering the original plugin to seed data
However, in our case, we want to add some extra logic to pre-seed the PolylineMeasurer with some latlngs that we pass down as a prop. I put in a pull request to the original plugin to add a .seed method. However, in the case of react-leaflet, we need to be more careful than using the code I put there. A lot of the methods required to draw polylines are only available after the L.Control.PolylineMeasure has been added to the map. I spent probably way too much time trying to figure out where in the react/react-leaflet lifecyle to intercept the instance of the polylineMeasure after it had been added to the map, so my eventual solution was to alter the source code of Leaflet.PolylineMeasure.
In the onAdd method, after all the code has run, we add in this code, which says that if you use a seedData option, it will draw that seed data once the control is added to the map:
// inside L.Control.PolylineMeasure.onAdd:
onAdd: function(map) {
// ... all original Leaflet.PolylineMeasure code here ...
if (this.options.seedData) {
const { seedData } = this.options;
seedData.forEach((polyline) => {
// toggle draw state on:
this._toggleMeasure();
// start line with first point of each polyline
this._startLine(polyline[0]);
// add subsequent points:
polyline.forEach((point, ind) => {
const latLng = L.latLng(point);
this._mouseMove({ latLng });
this._currentLine.addPoint(latLng);
// on last point,
if (ind === polyline.length - 1) {
this._finishPolylinePath();
this._toggleMeasure();
}
});
});
}
return this._container;
}
This code programatically calls all the same events that would be called if a user turned on the control, clicked around, and drew their lines that way.
Tying it together
So now our <PolylineMeasurer /> component takes as its props the options that would be fed to L.control.polylineMeasure, in addition to a new optional prop called seedData which will cause the map to be rendered with that seedData:
const Map = () => {
return (
<MapContainer {...mapContainerProps}>
<TileLayer url={url} />
<PolylineMeasurer
position="topleft"
clearMeasurementsOnStop={false}
seedData={seedData}
/>
</MapContainer>
);
};
Working Codesandbox
Caveat
If by some other mechanism in your app the seedData changes, you cannot expect the PolylineMeasurer component to react in the same way that normal React components do. In create leaflet, this control is added to the map once with the options you feed it, and that's it. While some react-leaflet-v3 component factory functions come with an update paramter, createcontrolcomponent does not (i.e. its first argument is a function which creates a control instance, but it does not accept a second argument to potentially update the control instance like, say, createlayercomponent does).
That being said, you can apply a key prop to the PolylineMeasurer component, and if your seedData is changed somewhere else in the app, also change the key, and the PolylineMeasurer will be forced to rerender and draw your new data.

Parent component gets updated Redux state first, but needs updated refs coming from children. Best practices suggested?

I have a container populated dynamically with elements based on an array in the Redux store. A code example follows.
const state = {
elements: [
{
id: "element_1",
attrs: {
width: 200,
height: 100,
backgroundColor: "#ff0000"
}
},
{
id: "element_2",
attrs: {
width: 50,
height: 300,
backgroundColor: "#00ffff"
}
}
]
};
// Elements Container (elements coming from Redux)
const ElementsContainer = ({ elements, elementsRefs }) => (
<>
{elements.map(element => (
<div
key={element.id}
ref={el => {
elementsRefs.current[element.id] = el;
}}
/>
))}
</>
);
// Parent container (elements coming from Redux)
const ParentContainer = ({ elements }) => {
const elementsRefs = React.useRef({});
React.useEffect(() => {
// ...some code to animate the elements through the refs
}, [elements]);
return (
<>
<ElementsContainer elementsRefs={elementsRefs} />
</>
);
};
The issue is that Redux dispatches the updated state first to the parent, then to the child (ElementsContainer). Whenever I add an element to the state, the refs are assigned only after useEffect (which is meant to trigger some DOM animations with the GSAP library leveraging the ref) has been executed, meaning that useEffect cannot find the ref yet.
So far this is how I solved:
I added an early return inside useEffect in case the ref doesn't exist
I created a state in the parent that keeps count of the elements manipulation
I pass down the setter of the state to the element, triggering it inside the ref callback
I added the state that keeps count as a dependency of useEffect
All of this causes two renders for each manipulation of the elements list. I really dislike this solution and I wonder whether you can suggest a better pattern.
I made a PoC of the app using Context instead of Redux and it works as intended (here you can find the fiddle), but I still would like to hear suggestions.

React :getting offsetTop value and accessing the DOM dynamically

I am currently working on a react app,
and I need to implement changes on the screen that occurs when an element is on a certain position on the screen (on certain offsetTopValue),
this should be done dynamically because every element has a unique id that is created dynamically.
(it also contains 3 purecompenets and 2 functional components down the hierarchy
and most props are passed down using context)
index.jsx
class Collection extends Component {
<CollectionContext.Provider
value={{state: this.state, cards: this.props.cards...}}>
.......
ideal: (
<CollectionLayout cards= {this.props.cards ..../>
.......
</CollectionContext.Provider>
}
layout.jsx
componentDidMount () {
this._bindScroll();
}
_handleScroll = () => {
var html = document.documentElement;
this.updateCardsPositions();
}
render(
...
return(
<CollectionContext.Consumer>
{context =>
<Loader
randomSpin= {this.props.randomSpin}
currentTopic = {this.props.currentTopic}
/>}
<CollectionContext.Consumer>
);
)
loader.jsx
export const Loader = ({ randomSpin, currentTopic}) => (
.....
{!context.state.spinning &&
context.cards.map((card, index) => <Card key={index} card=
{card}/>)}
cards.jsx
export const Card = ({ card } **id={card.id}**) => {
return (...);
}
what I do have: I have a unique card id that is passed to each card,
and I can access the cards id in the layout.
I tried using refs but am not yet sure about the proper way
to pass it through all those components and access it in ComponentDidMount()
in the layout.
I tried utilizing the context but it can be accessed only inside render and not on componentDidMount.
I am also not sure how should I use the refs callback:
({(el)=>{inputRef = el}}) with a dynamic value (every card has a unique id that should be refed.
any other suggestions on how to get the offset top values from the layout would help.
I will appreciate any help or a general solution direction with it.
Thank You!

Categories

Resources