react-google-maps multiple markers breaks on open - react-google-maps

My app loads all my markers just fine, but crashes when I try and click a marker. I get the following error:
"React.Children.only expected to receive a single React element child."
In the main map component I build the map and bring in all the markers from Firebase. I wanted to componentize the markers and build them in a separate file just to keep things a bit straighter in my head.
Here is my Map component
const MyMapComponent = compose(
withProps({
googleMapURL: "https://maps.googleapis.com/maps/api/js?key=xxxxx&v=3.exp&libraries=geometry,drawing,places",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `500px` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withScriptjs,
withGoogleMap
)((props) =>
<GoogleMap
defaultZoom={15}
defaultCenter={{ lat: 34.6781445, lng: -82.8455519 }}
>
{props.markers.map(marker => (
<LotMarker key={marker.key} index={marker.key} lat={marker.latitude} lng={marker.longitude} />
))}
</GoogleMap>
);
class TheMap extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
test:'test',
lots: [],
isOpen: false
};
}
componentWillMount() {
base.bindToState('lots', {
context: this,
state: 'lots',
asArray: true,
});
}
handleMarkerClick = (key) => {
console.log(key);
}
render() {
return (
<MyMapComponent
isMarkerShown={this.state.isMarkerShown}
onMarkerClick={this.handleMarkerClick}
markers={this.state.lots}
/>
)
}
}
export default TheMap;
Here I put the marker together. I've housed the InfoWindow component inside the marker and then ask it to show when the marker is clicked. However, when I do click a marker it just crashes the whole app and white screens.
This is my LotMarker component:
class LotMarker extends React.Component {
// Constructor.
constructor(props) {
// Super Props.
super(props);
// State.
this.state = {
isOpen: false
}
}
onToggleOpen = () => {
console.log(this);
this.setState({isOpen: !this.state.isOpen});
}
// Render.
render() {
// console.log(this);
// Extract Props.
const {
props: {
index,
lat,
lng,
open
}
} = this
return (
<Marker key={index} position={{ lat: lat, lng: lng }} onClick={this.onToggleOpen}>
{this.state.isOpen && <InfoWindow onCloseClick={this.onToggleOpen}>{index}</InfoWindow>}
</Marker>
)
}
}
export default LotMarker;

Alright, it turns out there was a really simple fix here. The content inside the InfoWindow has to be wrapped in a JSX compliant element.
Rather than this:
<InfoWindow onCloseClick={this.onToggleOpen}>{index}</InfoWindow>
I had to have this:
<InfoWindow onCloseClick={this.onToggleOpen}><span>{index}</span></InfoWindow>

Related

Map is not re centering on marker position in react google maps

react-google-maps is working fine,i use props to recenter the center position,but it seems map is not centering.Whats wrong in my code?
I have a map container and google map is a function base component.
import React, {useEffect, useState} from "react";
import {GoogleMap, InfoWindow, Marker, withGoogleMap, withScriptjs} from "react-google-maps";
import mapStyles from "./mapStyles";
import MapIcon from "../views/mapicon.png";
import MapMarked from "../views/mapMarked.png";
function Map(data) {
const cameras = data.data;
const [selectedPark, setSelectedPark] = useState(null);
const [cameraName,setCameraName]=useState(null);
const [center,setCenter]=useState(null);
const [zoom,setZoom]=useState(null)
useEffect(() => {
setCenter({lat: 23.8103, lng: 90.4125});
setZoom(11)
const listener = e => {
if (e.key === "Escape") {
setSelectedPark(null);
setZoom(11);
setCenter({lat: 23.8103, lng: 90.4125});
}
};
window.addEventListener("keydown", listener);
return () => {
window.removeEventListener("keydown", listener);
};
}, []);
const changeMap =(marker)=>{
const a= marker.lat, b=marker.lon;
const g= '{';
const v=':';
const centers=g.concat('lat',v,a,',','lng',v,b,'}');
setSelectedPark(marker);
setCameraName(marker.name);
setCenter(centers)
setZoom(15)
}
return (
<GoogleMap
zoom={zoom}
center={center}
defaultOptions={{ styles: mapStyles,
// gestureHandling:'greedy',
zoomControlOptions: { position: 14 },
}}
>
{cameras.map(marker=> (
<Marker
key={marker.id}
position={{
lat: marker.lat,
lng: marker.lon
}}
onClick={() => {
changeMap(marker);
}}
icon={{
url: cameraName !== marker.name ? MapIcon : MapMarked ,
scaledSize: new window.google.maps.Size(40, 40)
}}
/>
))}
{selectedPark && (
<InfoWindow
onCloseClick={() => {
setSelectedPark(null);
setCameraName(null);
setZoom(11);
setCenter({lat: 23.8103, lng: 90.4125});
}}
position={{
lat: selectedPark.lat,
lng: selectedPark.lon
}}
options={{
alignBottom: true,
pane: 'mapPane',
pixelOffset: new window.google.maps.Size(0, -50),
boxStyle: {
width: '80px'
},
closeBoxURL: ``,
enableEventPropagation: true
}}
>
<div>
<h3>{selectedPark.name}</h3>
<p>{selectedPark.address}</p>
</div>
</InfoWindow>
)}
</GoogleMap>
);
}
const MapWrapped = withScriptjs(withGoogleMap(Map));
class GoogleMapontainer extends React.Component {
render() {
if(this.props.data.length>0) {
return (
<div
style={{
position: "relative",
height: "calc(100vh - 175px)",
backgroundColor: "#F2F3F5"
}}
>
<MapWrapped
googleMapURL={`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=key`}
loadingElement={<div style={{height: `100%`}}/>}
containerElement={<div style={{height: `100%`}}/>}
mapElement={<div style={{height: `100%`}}/>}
data={this.props.data}
/>
</div>
);
}
else
return <div> Loading....</div>
}
}
export default GoogleMapontainer;

React Leaflet: Updating coords of multiple markers in state

I can't seem to figure out how to properly update the coords of individual markers after storing them in the state. How it works currently is that when you click somewhere on the map, it adds a marker and stores its initial position in the state (in markerData) which is then displayed on the map through a map function. You can move the individual markers around but I'm having difficulty figuring out a possible solution to updating the specific markers position so that eventually I can send and store the marker information in the back end.
Here is my current code.
import React, { Component } from 'react';
import { ImageOverlay, Map, Marker, Popup } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import 'leaflet/dist/leaflet.js';
import L from 'leaflet';
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
iconUrl: require('leaflet/dist/images/marker-icon.png'),
shadowUrl: require('leaflet/dist/images/marker-shadow.png')
});
export default class CustomMapExample extends Component {
constructor(props) {
super(props);
this.state = {
center: {
lat: 512,
lng: 1024,
},
zoom: 1,
draggable: true,
dimensions: [1024, 2048],
markerData: [],
};
}
toggleDraggable = () => {
this.setState({ draggable: !this.state.draggable })
}
addMarker = (event) => {
const {markerData} = this.state
const coords = event.latlng
markerData.push(coords)
this.setState({markerData})
}
updateMarker = (event) => {
console.log(event)
}
render () {
const boundOrigin = [0, 0];
const bounds = [boundOrigin, this.state.dimensions];
const position = [this.state.center.lat, this.state.center.lng]
return (
<div>
<Map
id="map"
crs={L.CRS.Simple}
minZoom={-1}
bounds={bounds}
center={position}
zoom={this.state.zoom}
onClick={this.addMarker}
>
<ImageOverlay
url='http://www.online-tabletop.com/wp-content/uploads/2017/01/tutoriala.jpg'
bounds={bounds}
/>
{this.state.markerData.map((element, index) =>
<Marker
key={index}
position={element}
draggable={this.state.draggable}
onDragend={this.updateMarker}
>
<Popup>
<span onClick={this.toggleDraggable}>
{this.state.draggable ? `Hello` : 'MARKER FIXED'}
</span>
</Popup>
</Marker>
)}
</Map>
</div>
);
}
}
Since Marker accepts options object as a second parameter, marker index could be introduced to reference which marker is getting updated:
<Marker
key={index}
marker_index={index}
position={element}
draggable={this.state.draggable}
onDragend={this.updateMarker}
/>
And then markerData state could be updated like this once the marker is dragged:
updateMarker = event => {
const latLng = event.target.getLatLng(); //get updated marker LatLng
const markerIndex = event.target.options.marker_index; //get marker index
//update
this.setState(prevState => {
const markerData = [...prevState.markerData];
markerData[markerIndex] = latLng;
return { markerData: markerData };
});
};
Demo

Reactjs how to get value from selected element

so I have this code for posting to my backend API. Normal form perfectly fine; I managed to post to my database. So I add a Cascader from Ant Design CSS Framework, and every time I selected the value, it produced an error
TypeError: Cannot read property 'value' of undefined
Here is the code:
import React from 'react';
import axios from 'axios';
import { Button, Cascader, Form, Input, Modal } from 'antd';
const FormProduct = Form.Item;
const computerType = [
{
value: 'computer',
label: 'Computer',
},
{
value: 'laptop',
label: 'Laptop',
}
]
export default class FormInventory extends React.Component {
state = {
category: '',
productname: '',
};
handleCategoryChange = event => { this.setState({ category: event.target.value }) }
handleProductNameChange = event => { this.setState({ productname: event.target.value }) }
handleSubmit = event => {
event.preventDefault();
axios.post('myapi',
{
category: this.state.category,
productname: this.state.productname,
})
.then(
function success() {
const modal = Modal.success({
title: 'Success',
content: 'Data successfully add',
});
setTimeout(() => modal.destroy(), 2000);
}
)
}
render() {
return (
<Form onSubmit={this.handleSubmit}>
<FormProduct {...formProductLayout} label="Computer Category">
<Cascader options={computerType} category={this.state.value} onChange={this.handleCategoryChange} />
</FormProduct>
<FormProduct {...formProductLayout} label="Product Name">
<Input type="text" productname={this.state.productname} onChange={this.handleProductNameChange} />
</FormProduct>
<FormProduct wrapperCol={{ span: 12, offset: 2 }}>
<Button type="primary" htmlType="submit">
Add Item
</Button>
</FormProduct>
</Form>
)
}
}
You need to either bind your event handlers in the constructor or use arrow function.
Option 1: Bind
constructor(props) {
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
Option 2: Arrow function
<Input onChange={(e) => this.handleChange(e)} />
According to antd docs you don't need event.target.
https://ant.design/components/cascader/
handleCategoryChange = category => { this.setState({ category }) }
The code above will work fine.

On refresh react application I need to get from componentWillReceiveProps values for input text

I have the following problem and I really need help on that.
export class DeviceEdit extends React.PureComponent<Props> {
constructor(props) {
super(props);
this.state = {
value: ''
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
let data = this.props.devices.data.find(device => device.id ===
`${deviceID}`) || {};
this.setState({ value: data.name })
}
componentWillMount() {
let data = this.props.devices.data.find(device => device.id ===
`${deviceID}`) || {};
this.setState({ value: data.name })
}
componentWillReceiveProps(newProps) {
let data = newProps.devices.data.find(device => device.id ===
`${deviceID}`) || {};
this.setState({ value: data.name })
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const { error } = this.props;
return (
<FormLabel>Internal ID</FormLabel>
<input type="text" defaultValue={this.state.value} onChange= .
{this.handleChange} />
</Form.Label>)
}
}
So what I want is that when I refresh the page, I want to get the the this.state.value on my input.. which in this case I am not able to do that. So I would like to know what I am doing wrong here. If I set it on value on the input I did get what I want, but then I have an warning like that:
A component is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component
What can I do?
Actually, you should handle changes there and you can use just value on the input field instead of defaultValue.
For example:
export class AdminDeviceEdit extends React.PureComponent<Props> {
constructor(props) {
super(props);
this.state = {
value: '',
// if it comes from props by default
// you can use, if not just leave as it is
value: props.value
};
}
handleChange = e => {
this.setState({value: e.target.value});
}
render() {
const { error } = this.props;
return (
<form>
<FormLabel>Internal ID</FormLabel>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</form>
)
}
}
Hope it will helps.
So from what I understand you want to make controlled input but use props.value as a default value. What if you do:
export class AdminDeviceEdit extends React.PureComponent<Props> {
constructor(props) {
super(props);
this.state = {
value: props.value,
};
this.handleChange = this.handleChange.bind(this);
}
componentWillReceiveProps(newProps) {
if(this.props.value !== newProps.value) {
this.setState({ value: newProps.value }) // reset input value
}
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const { error } = this.props;
return (
<FormLabel>Internal ID</FormLabel>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</Form.Label>)
}
}
Certainly get rid of componentWillMount and componentDidMount. You don't need them here.

Dynamically include files (components) and dynamically inject those components

Looking around the next I could not find the answer: How do I dynamicly include a file, based on prop change per say: here some sudo code to intrastate what I'm trying to do!
class Test extends React.Component {
constructor(props){
super(props)
this.state = { componentIncluded: false }
includeFile() {
require(this.props.componetFileDir) // e.g. ./file/dir/comp.js
this.setState({ componentIncluded: true });
}
render() {
return(
<div className="card">
<button onClick={this.includeFile}> Load File </button>
{ this.state.componentIncluded &&
<this.state.customComponent />
}
</div>
)
}
}
so this.props.componetFileDir has access to the file dir, but I need to dynamically include it, and can't really do require() as its seems to running before the action onClick get called. Any help would be great.
Em, Your code looks a bit wrong to me. So I created a separate demo for dynamic inject components.
While in different situation you can use different React lifecycle functions to inject your component. Like componentWillReceiveProps or componentWillUpdate.
componentDidMount() {
// dynamically inject a Button component.
System.import('../../../components/Button')
.then((component) => {
// update the state to render the component.
this.setState({
component: component.default,
});
});
}
render() {
let Button = null;
if (this.state.component !== null) {
Button = this.state.component;
}
return (
<div>
{ this.state.component !== null ? <Button>OK</Button> : false }
</div>
);
}
After you edited your code, it should be something similar to below:
class Test extends React.Component {
constructor(props){
super(props)
this.state = { customComponent: null }
this.includeFile = this.includeFile.bind(this);
}
includeFile() {
System.import(this.props.componetFileDir)
.then((component) => {
this.setState({ customComponent: component.default });
});
}
render() {
return(
<div className="card">
<button onClick={this.includeFile}> Load File </button>
{
this.state.customComponent
}
</div>
)
}
}