Polygon fill color not working properly (React Native maps) - reactjs

I am using Google Maps on iOS and I have Polygons. (react-native-maps)
Before update (to version 0.18.3. - at the moment I am not able to update to latest version) everything works properly, but from now fill color gets weird results.
Sometimes color is ok, sometimes it is not proper, no rules.
On android everything works well.
export const Polygon = (props) => {
return (
<MapView.Polygon
coordinates={ props.selectedAreas }
fillColor={ props.fillColor }
strokeColor={ props.strokeColor }
/>
)
};

Worked for me using the fix from https://github.com/react-native-community/react-native-maps/issues/3025#issuecomment-538345230
import React from 'react';
import { Polygon } from 'react-native-maps';
function CustomPolygon({ onLayout, ...props }) {
const ref = React.useRef();
function onLayoutPolygon() {
if (ref.current) {
ref.current.setNativeProps({ fillColor: props.fillColor });
}
// call onLayout() from the props if you need it
}
return <Polygon ref={ref} onLayout={onLayoutPolygon} {...props} />;
}
export default CustomPolygon;
It is not very pretty but I guess it will have to do until the upstream bug is fixed.

Related

Mobile Safari Not Showing My Video Texture

I have a question similar to this one, but in my case, it's iOS causing troubles (not macOS, which I haven't tried yet), so I hope it's OK to post this as well. I tried to create a video texture in Three.js and can't bring it to work on mobile Safari (iOS 15.4). Here is my code, which I tried to tidy up as much as possible:
import React, { useEffect, useRef } from "react";
import * as THREE from "three";
import { Canvas } from "#react-three/fiber";
import "./styles.css";
const Screen = () => {
const meshRef = useRef();
useEffect(() => {
const vid = document.createElement("video");
vid.src = "/test.mp4";
vid.crossOrigin = "Anonymous";
vid.loop = vid.muted = vid.playsInline = true;
vid.play();
meshRef.current.material.map = new THREE.VideoTexture(vid);
});
return (
<mesh ref={meshRef}>
<planeGeometry attach="geometry" />
</mesh>
);
};
const App = () => {
return (
<Canvas camera={{ fov: 25 }}>
<Screen />
</Canvas>
);
};
export default App;
Please tell me if I'm doing something wrong here. The test.mp4 is from this URL. I also tried to place the video as HTML element, instead of creating it dynamically, then the video itself plays fine, but not the video texture.
Also, just curious, but why isn't meshRef.current available in a useEffect in the main component, but useEffect inside of Screen, which is placed inside of Canvas, is OK?
Apparently it's a problem with video file formats. Tried an example video from Three.js and it worked.
To those of you looking for the solution , you need to add
vid.playsInline=true;
for mobile ios devices.
I had the same problem. I had to set the 'playsinline' attribute in a very specific way.
video.playsinline= true did not work but video.setAttribute('playsinline', true)
did work.
Hope this helps

Custom button on the leaflet map with React-leaflet version3

I'm a new leaflet learner with React typescript. Want to create a custom button on the map. On clicking the button a popup will appear. I saw many example but they are all based on older version and I also tried to create my own but no luck. The documentation also not providing much help. Even a functional custom control component is also very effective for my app. Any help on this will be much appreciated. Here is my code,
Custom button
import React, { Component } from "react";
import { useMap } from "react-leaflet";
import L, { LeafletMouseEvent, Map } from "leaflet";
class Description extends React.Component<{props: any}> {
createButtonControl() {
const MapHelp = L.Control.extend({
onAdd: (map : Map) => {
const helpDiv = L.DomUtil.create("button", ""); //how to pass here the button name and
//other property ?
//a bit clueless how to add a click event listener to this button and then
// open a popup div on the map
}
});
return new MapHelp({ position: "bottomright" });
}
componentDidMount() {
const { map } = this.props as any;
const control = this.createButtonControl();
control.addTo(map);
}
render() {
return null;
}
}
function withMap(Component : any) {
return function WrappedComponent(props : any) {
const map = useMap();
return <Component {...props} map={map} />;
};
}
export default withMap(Description);
The way I want to call it
<MapContainer
center={defaultPosition}
zoom={6}
zoomControl={false}
>
<Description />
<TileLayer
attribution="Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL."
url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png"
/>
<ZoomControl position={'topright'}/>
</MapContainer>
You're close. Sticking with the class component, you just need to continue creating your buttons instance. You can use a prop on Description to determine what your button will say and do:
<Description
title={"My Button Title"}
markerPosition={[20.27, -157]}
description="This is a custom description!"
/>
In your decsription's createButtonControl, you're almost there. You just need to fill it out a bit:
createButtonControl() {
const MapHelp = L.Control.extend({
onAdd: (map) => {
const helpDiv = L.DomUtil.create("button", "");
this.helpDiv = helpDiv;
// set the inner content from the props
helpDiv.innerHTML = this.props.title;
// add the event listener that will create a marker on the map
helpDiv.addEventListener("click", () => {
console.log(map.getCenter());
const marker = L.marker()
.setLatLng(this.props.markerPosition)
.bindPopup(this.props.description)
.addTo(map);
marker.openPopup();
});
// return the button div
return helpDiv;
}
});
return new MapHelp({ position: "bottomright" });
}
Working codesandbox
There's a million ways to vary this, but hopefully that will get you going.

Moving slider with Cypress

I've got a Slider component from rc-slider and I need Cypress to set the value of it.
<Slider
min={5000}
max={40000}
step={500}
value={this.state.input.amount}
defaultValue={this.state.input.amount}
className="sliderBorrow"
onChange={(value) => this.updateInput("amount",value)}
data-cy={"input-slider"}
/>
This is my Cypress code:
it.only("Changing slider", () => {
cy.visit("/");
cy.get(".sliderBorrow")
.invoke("val", 23000)
.trigger("change")
.click({ force: true })
});
What I've tried so far does not work.
Starting point of slider is 20000, and after test runs it goes to 22000, no matter what value I pass, any number range.
Looks like it used to work before, How do interact correctly with a range input (slider) in Cypress? but not anymore.
The answer is very and very simple. I found the solution coincidentally pressing enter key for my another test(date picker) and realized that pressing left or right arrow keys works for slider.
You can achieve the same result using props as well. The only thing you need to do is to add this dependency: cypress-react-selector and following instructions here: cypress-react-selector
Example of using {rightarrow}
it("using arrow keys", () => {
cy.visit("localhost:3000");
const currentValue = 20000;
const targetValue = 35000;
const increment = 500;
const steps = (targetValue - currentValue) / increment;
const arrows = '{rightarrow}'.repeat(steps);
cy.get('.rc-slider-handle')
.should('have.attr', 'aria-valuenow', 20000)
.type(arrows)
cy.get('.rc-slider-handle')
.should('have.attr', 'aria-valuenow', 35000)
})
#darkseid's answer helped guide me reach an optimal solution.
There are two steps
Click the slider's circle, to move the current focus on the slider.
Press the keyboard arrow buttons to reach your desired value.
My slider jumps between values on the sliders, therefore this method would work. (I am using Ion range slider)
This method doesn't require any additional depedency.
// Move the focus to slider, by clicking on the slider's circle element
cy.get(".irs-handle.single").click({ multiple: true, force: true });
// Press right arrow two times
cy.get(".irs-handle.single").type(
"{rightarrow}{rightarrow}"
);
You might be able to tackle this using Application actions, provided you are able to modify the app source code slightly.
Application actions give the test a hook into the app that can be used to modify the internal state of the app.
I tested it with a Function component exposing setValue from the useState() hook.
You have used a Class component, so I guess you would expose this.updateInput() instead, something like
if (window.Cypress) {
window.app = { updateInput: this.updateInput };
}
App: index.js
import React, { useState } from 'react';
import { render } from 'react-dom';
import './style.css';
import Slider from 'rc-slider';
import 'rc-slider/assets/index.css';
function App() {
const [value, setValue] = useState(20000);
// Expose the setValue() method so that Cypress can set the app state
if (window.Cypress) {
window.app = { setValue };
}
return (
<div className="App">
<Slider
min={5000}
max={40000}
step={500}
value={value}
defaultValue={value}
className="sliderBorrow"
onChange={val => setValue(val)}
data-cy={"input-slider"}
/>
<div style={{ marginTop: 40 }}><b>Selected Value: </b>{value}</div>
</div>
);
}
render(<App />, document.getElementById('root'));
Test: slider.spec.js
The easiest way I found assert the value in the test is to use the aria-valuenow attribute of the slider handle, but you may have another way of testing that the value has visibly changed on the page.
describe('Slider', () => {
it("Changing slider", () => {
cy.visit("localhost:3000");
cy.get('.rc-slider-handle')
.should('have.attr', 'aria-valuenow', 20000)
cy.window().then(win => {
win.app.setValue(35000);
})
cy.get('.rc-slider-handle')
.should('have.attr', 'aria-valuenow', 35000)
})
})
For whoever comes across this with Material UI/MUI 5+ Sliders:
First off, this github issue and comment might be useful: https://github.com/cypress-io/cypress/issues/1570#issuecomment-606445818.
I tried changing the value by accessing the input with type range that is used underneath in the slider, but for me that did not do the trick.
My solution with MUI 5+ Slider:
<Slider
disabled={false}
step={5}
marks
data-cy="control-percentage"
name="control-percentage"
defaultValue={0}
onChange={(event, newValue) =>
//Handle change
}
/>
What is important here is the enabled marks property. This allowed me to just click straight on the marks in the cypress test, which of course can also be abstracted to a support function.
cy.get('[data-cy=control-percentage]').within(() => {
// index 11 represents 55 in this case, depending on your step setting.
cy.get('span[data-index=11]').click();
});
I got this to work with the popular react-easy-swipe:
cy.get('[data-cy=week-picker-swipe-container]')
.trigger('touchstart', {
touches: [{ pageY: 0, pageX: 0 }]
})
.trigger('touchmove', {
touches: [{ pageY: 0, pageX: -30 }]
})

How can I reset a dragged component to its original position with react-draggable?

I try to implement a function in my app that allows the user to reset all the components that he dragged around to be reset to their original position.
I assume that this functionality exists in react-draggable because of this closed and released issue: "Allow reset of dragging position" (https://github.com/idanen/react-draggable/issues/7). However I did not find any hint in the documentation (https://www.npmjs.com/package/react-draggable).
There was one question with the same content in stackoverflow, but it has been removed (https://stackoverflow.com/questions/61593112/how-to-reset-to-default-position-react-draggable).
Thanks for your help :-)
The referenced issue on the GitHub references a commit. After taking a look at the changes made in this commit, I found a resetState callback added to the useDraggable hook. In another place in the commit, I found a change to the test file which shows usage of the hook.
function Consumer(props) {
const {
targetRef,
handleRef,
getTargetProps,
resetState,
delta,
dragging
} = useDraggable(props);
const { style = defaultStyle } = props;
return (
<main
className='container'
ref={targetRef}
data-testid='main'
style={style}
{...getTargetProps()}
>
{dragging && <span>Dragging to:</span>}
<output>
{delta.x}, {delta.y}
</output>
<button className='handle' ref={handleRef}>
handle
</button>
<button onClick={resetState}>reset</button>
</main>
);
}
The hook returns a set of callbacks, including this callback, which can be used to reset the state of the draggable.
I wanted the component to reset back to its original position when the component was dropped.
Using hooks I monitored if the component was being dragged and when it was false reset the position otherwise it would be undefined.
export default function DraggableComponent(props: any) {
const {label} = props
const [isDragging, setIsDragging] = useState<boolean>(false)
const handleStart = (event: any, info: DraggableData) => {
setIsDragging(true)
}
const handleStop = (event: any, info: DraggableData) => {
setIsDragging(false)
}
return (
<Draggable
onStart={handleStart}
onStop={handleStop}
position={!isDragging? { x: 0, y: 0 } : undefined}
>
<Item>
{label}
</Item>
</Draggable>
)
}
Simple approach would be:
creating a new component to wrap our functionality around the Draggable callbacks
reset position when onStop callback is triggered
Example:
import { useState } from 'react';
import Draggable, { DraggableData, DraggableEvent, DraggableProps } from 'react-draggable';
export function Drag({ children, onStop, ...rest }: Partial<DraggableProps>) {
const initial = { x: 0, y: 0 }
const [pos, setPos] = useState(initial)
function _onStop(e: DraggableEvent, data: DraggableData){
setPos(initial)
onStop?.(e, data)
}
return (
<Draggable position={pos} onStop={_onStop} {...rest}>
{children}
</Draggable>
)
}
Usage:
export function App() {
return (
<Drag> Drag me </Drag>
)
}
Note that this answer does not work.
None of these approaches worked for me, but tobi2424's post on issue 214 of the Draggable repo did. Here's a minimal proof-of-concept:
import React from "react";
import Draggable from "react-draggable";
const DragComponent = () => {
// Updates the drag position parameter passed to Draggable
const [dragPosition, setDragPosition] = React.useState(null);
// Fires when the user stops dragging the element
const choiceHandler = () => {
setDragPosition({x: 0, y: 0});
};
return (
<Draggable
onStop={choiceHandler}
position={dragPosition}
>
Drag me
</Draggable>
);
};
export default DragComponent;
Edit
The code above works intermittently but not particularly well. As far as I can work out, react-draggable stores data about the position of the dragged element somewhere outside of React, in order to preserve the position of the element between component refreshes. I was unable to determine how to reset the position of the element on command and none of the other example code solves the problem for me.
You can do this in a very haphazard manner. There may be another way to set state more safely on this but I didn't look too deeply into it.
import React from 'react';
export default class 😊 extends Component {
constructor(props) {
super(props);
this.draggableEntity = React.createRef();
}
resetDraggable() {
try {
this.draggableEntity.current.state.x = 0;
this.draggableEntity.current.state.y = 0;
} catch (err) {
// Fail silently
}
}
render() {
return (
<Draggable
ref={this.draggableEntity}
>
<img onClick={(e) => {this.resetDraggable()}}></img>
</Draggable>
)
}
}
There happens to be another way! You can use it's exposed ref element to reset its offset. This can be achieved like so:
import React, {useRef, useCallback} from "react";
import Draggable from "react-draggable";
const DragComponent = () => {
// Updates the drag position parameter passed to Draggable
const [dragPosition, setDragPosition] = React.useState(null);
const draggerRef = useRef(null);
// Fires when the user stops dragging the element
const resetDrag = useCallback(() => {
setDragPosition({x: 0, y: 0});
draggerRef.current?.setState({ x: 0, y: 0 }); // This is what resets it!
}, [setDragPosition, draggerRef]);
return (
<Draggable
ref={draggerRef}
onStop={resetDrag}
position={dragPosition}
>
Drag me
</Draggable>
);
};
export default DragComponent;

close popup react-leaflet after user click on button in popup

So basically want to make custom close for react-leaflet Popup component, seams that is not a big problem to do with native API leaflet but with react component from react-leaflet I can't find the solution.
at the moment, the only way I found to close the popup is the following:
constructor(props){
super(props);
this.popup = React.createRef();
}
// the magic
closePopusOnClick(){
this.popup.current.leafletElement.options.leaflet.map.closePopup();
}
render(){
return <Marker position={[this.props.lat, this.props.lng]}>
<Popup ref={this.popup}>
<Button onClick={this.closePopusOnClick}>Close popup</Button>
</Popup>
</Marker>;
}
Hope it helps!
In "react-leaflet": "^3.0.2" I managed to close the popup with:
popupRef.current._closeButton.click()
Not very nice comparing to a future Popup.close() method which MUST work out-of-box, but gets the job done...
I ended up with a similar solution to Luca's Answer, so I thought I'd add it as an answer too. I needed to close all popups when moving or zooming the map and ended up with the following:
import React, { useRef } from "react";
import { Map } from "react-leaflet"
export default () => {
const mapRef = useRef(null);
const closePopups = () => {
mapRef.current.leafletElement.closePopup();
};
const handleOnDragend = e => {
closePopups();
};
const handleOnZoomend = e => {
closePopups();
};
if (typeof window === 'undefined') {
return null;
}
return (
<Map
ref={mapRef}
onDragend={handleOnDragend}
onZoomend={handleOnZoomend}
>
</Map>
)
}
This can, however, be extended so that anything can call the closePopups method.
I found the working solution for react-leaflet v3 by modifying these two links codesandbox https://codesandbox.io/s/4ws0i and https://stackoverflow.com/a/67750291/8339172
here is the function to hide the Popup component
const hideElement = () => {
if (!popupElRef.current || !map) return;
map.closePopup();
};
here is the Popup component
<Popup ref={popupElRef} closeButton={false}>
<button onClick={hideElement}>Close popup</button>
</Popup>

Resources