I am trying to build a Vue component that takes in input an array of gps (lat/lng) data and which have to be drawn on a map using a google maps polyline. It is working until this point. The problem rises when I try to use map.fitbounds in order to manage the zoom and center out the map to that polyline. As per vue3-google-map documentation I tried to create a ref to the map object with ref="mapRef". Unfortunately in the mounted() hook it seems that I can'f find that object, inside Vue DevTools it will later show up.
<template>
<GoogleMap ref="mapRef" api-key="<myapikey>" style="width: 100%; height: 500px" :center="center" :zoom="15">
<Polyline :options="path" />
</GoogleMap>
</template>
<script>
import {
defineComponent,
ref
} from 'vue'
import {
GoogleMap,
Polyline
} from 'vue3-google-map'
export default defineComponent({
components: {
GoogleMap,
Polyline
},
setup() {
const mapRef = ref(null)
return {
mapRef
}
},
methods: {
managebounds(path) {
this.intervalid = setInterval(function () {
if (!this.bounds && !this.loaded) {
if (window.google && window.google.maps && path) {
this.bounds = new window.google.maps.LatLngBounds()
this.loaded = true
clearInterval(this.intervalid)
console.log("AFTER CLEAR")
this.bounds.extend(path.path[0]) //i take the first and last point of the polyline
this.bounds.extend(path.path[path.path.length - 1])
var extendBy = 0.001;
console.log("EXTEND1")
var point1 = new window.google.maps.LatLng(
this.bounds.getNorthEast().lat() + extendBy,
this.bounds.getNorthEast().lng() + extendBy
)
var point2 = new window.google.maps.LatLng(
this.bounds.getSouthWest().lat() - extendBy,
this.bounds.getSouthWest().lng() - extendBy
)
this.bounds.extend(point1);
console.log("EXTEND2")
this.bounds.extend(point2);
console.log("FITTING BOUNDS")
this.intervalid = setInterval(function () {
if (this.$refs.mapRef.value?.ready) {
console.log("LOADED")
this.$refs.mapRef.value.map.fitBounds(this.bounds); //here mapRef is undefined
clearInterval(this.intervalid)
} else {
console.log("NOT LOADED")
}
}, 1000)
} else {
console.log("OUT")
}
}
}, 500)
}
},
mounted() {
this.$nextTick(function () {
this.managebounds(this.path)
})
},
data() {
return {
path: {
path: this.gpspath.path,
strokeColor: this.gpspath.color
},
bounds: null,
loaded: false,
intervalid: -1
}
},
props: {
"gpspath": {
type: [],
default: [{}]
}
}
})
</script>
Any hint about fixing this issue?
I had to read again the Vue documentation about computed properties and figured out is was a bad idea trying to build a Method.
I made a function inside the setup() hook and called that from my computed data.
The working result is in the following code:
setup() {
const mapRef = ref(null)
function centermap(start, end){
if (mapRef.value?.ready) {
const gmap = mapRef.value.map;
const api = mapRef.value.api;
this.bounds = new api.LatLngBounds();
this.bounds.extend(start);
this.bounds.extend(end);
gmap.fitBounds(this.bounds);
}else{
console.log("NOT READY")
}
}
return {
mapRef, centermap
}
},
computed: {
path() {
if(this.gpspath){
let filteredpath = this.gpspath.path.filter(x=>Math.abs(x.lat)>1)
if(filteredpath && filteredpath.length>1)
this.centermap(filteredpath[0], filteredpath[filteredpath.length-1])
return {
path: filteredpath,
strokeColor: this.gpspath.color
}
}else{
return {
path: [],
strokeColor: "#ffffff"
}
}
}
}
I also faced this problem; here is my solution:
Page with map:
import { mapFitBounds } from "#/composables/mapFitBounds";
const mapRef = ref(null);
watch(
() => mapRef.value?.ready,
(ready) => {
if (!ready) return;
mapFitBounds(mapRef, markersArray);
}
);
composables/mapFitBounds.js:
export function mapFitBounds(mapRef, markers) {
let bounds;
const api = mapRef.value.api;
const map = mapRef.value.map;
bounds = new api.LatLngBounds();
for (let i = 0; i < markers.length; i++) {
bounds.extend(markers[i]);
}
map.fitBounds(bounds);
}
Related
here I get a bug when trying the leaflet-routing-machine lib.
The bug is that the "Waypoints" section renders 2 times.
why is it able to render 2 times? Can you guys help me? Thank you
My Code in below =>
My Code =
import { useEffect } from "react";
import L from "leaflet";
import "leaflet-routing-machine/dist/leaflet-routing-machine.css";
import "leaflet-routing-machine";
import { useMap } from "react-leaflet";
L.Marker.prototype.options.icon = L.icon({
iconUrl: "https://unpkg.com/leaflet#1.7.1/dist/images/marker-icon.png",
});
export default function Routing() {
const map = useMap();
const routingControl = L.Routing.control({
waypoints: [
L.latLng(-6.3094117, 106.8240261),
L.latLng(-6.2185648, 106.7996082),
],
lineOptions: {
styles: [{ color: "#6FA1EC", weight: 4 }],
},
routeWhileDragging: true,
draggableWaypoints: true,
fitSelectedRoutes: true,
}).addTo(map);
useEffect(() => {
return () => map.removeControl(routingControl);
}, [map, routingControl]);
function createButton(label, container) {
let btn = L.DomUtil.create("button", "", container);
btn.setAttribute("type", "button");
btn.innerHTML = label;
return btn;
}
map.on("click", function (e) {
let container = L.DomUtil.create("div"),
startBtn = createButton("Start from this location", container),
destBtn = createButton("Go to this location", container);
container.setAttribute("class", "leaflet-popup-btn-box");
L.DomEvent.on(startBtn, "click", function () {
routingControl.spliceWaypoints(0, 1, e.latlng);
map.closePopup();
});
L.DomEvent.on(destBtn, "click", function () {
routingControl.spliceWaypoints(
routingControl.getWaypoints().length - 1,
1,
e.latlng
);
map.closePopup();
});
L.popup().setContent(container).setLatLng(e.latlng).openOn(map);
});
return null;
}
I cant deal with that problem.
I got object locations. there are objects with info about position, function and icon color.
I send it to Map. At start it shows ok but then when i filter the array and send new ones to show it doesnt render new ones.
I add state to class and setState it- after check i see in console that everything is ok with changes- but markers are still the same.
Please help
locations is also a state in parent stateless component.
Map Component
import React, {Component} from 'react'
import GoogleMapReact from 'google-map-react'
import MarkerClusterer from '#google/markerclusterer'
export default class GoogleMapContainer extends Component {
state = {
locationsToShow: null
}
componentDidMount () {
const script = document.createElement('script')
script.src = 'https://developers.google.com/maps/documentation/javascript/examples /markerclusterer/markerclusterer.js'
script.async = true
document.body.appendChild(script)
}
componentDidUpdate(prevProps) {
if (this.props.locations !== prevProps.locations) {
this.setState({locationsToShow: this.props.locations})
}
}
static defaultProps = {
center: {
lat: 59.95,
lng: 30.33
},
zoom: 11
}
render () {
const setGoogleMapRef = (map, maps) => {
this.googleMapRef = map
this.googleRef = maps
let locations = this.props.locations
console.log('locations w propsie')
let markers = this.state.locationsToShow && this.state.locationsToShow.map((location) => {
let item = new this.googleRef.Marker({position: location.position, icon: location.icon })
google.maps.event.addListener(item, 'click', location.fn)
return item
})
console.log('markerow jest')
let markerCluster = new MarkerClusterer(map, markers, {
imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m',
gridSize: 60,
minimumClusterSize: 2
})
}
return (
<GoogleMapReact
bootstrapURLKeys={{key: `apiKey`}}
yesIWantToUseGoogleMapApiInternals
onGoogleApiLoaded={({map, maps}) => setGoogleMapRef(map, maps)}
defaultCenter={{lat: 52.193275, lng: 20.930372}}
defaultZoom={7}
options={{streetViewControl: true}}
/>
)
}
}
parent comp element
let locationsList = filteredData.map((el) => {
const lati = Number(el.location.latitude).toFixed(6)
const longi = Number(el.location.longitude).toFixed(6)
return {position: { lat: Number(lati), lng: Number(longi) }, icon: typeOfIcon(el.status), fn: ()=>{
setIsOpen(true)
setData(el)
} }
})
setLocations(locationsList)
setGoogleMapRef is only called when the map component is instantiated I am guessing (you want this). so you need to access the map and update your locations in your render function outside of the setGoogleMapRef callback.
something like the following:
render () {
const setGoogleMapRef = (map, maps) => {
this.googleMapRef = map
this.googleRef = maps
this.markerCluster = //...
};
if (this.googleMapRef) {
let locations = this.props.locations
console.log('locations w propsie')
let markers = this.state.locationsToShow &&
this.state.locationsToShow.map((location) => {
let item = new this.googleRef.Marker({position: location.position, icon: location.icon })
google.maps.event.addListener(item, 'click', location.fn)
return item
})
console.log('markerow jest')
// update marker cluster
}
return //...;
}
I've been banging my head against the wall with google maps. See my initial issue here
I fixed the issue of using a react component as a map marker, by creating a portal with the OverlayView into a div. But now I'm faced with a bump in a rug now where I am now unsure how to create clustered markers?
Previously I used #googlemaps/markerclustererplus to create a cluster from an array of of markers created from a class CustomMarker that generates an OverlayView with the code snippet below.
const markers = mapMarkers.map((m) => {
const { position, icon, draggable } = m;
const marker = new CustomMarker(
position,
draggable,
icon,
map,
onChildMouseUp
);
return marker;
});
// Add a marker clusterer to manage the markers.
new MarkerClusterer(map, markers, {
calculator: (gMarkers, numStyles) => {
let index = 0;
const count = gMarkers.length;
let dv = count;
while (dv !== 0) {
dv = parseInt(dv / 10, 10);
index += 1;
}
index = Math.min(index, numStyles);
return {
text: `${count}`,
index
};
},
gridSize: 50,
styles: [
{
className: "clusterMarker",
width: 50,
height: 18
}
]
});
Now I'm confused how to populate the 2nd argument in MarkerClusterer with the markers now I add markers in the components return statement
<Map id="map" mapHeight={height} mapWidth={width}>
{gmap && mapMarkers?.length > 0 ? (
<>
{mapMarkers.map((m, i) => {
return (
<CustomMarkerNew key={i} position={m.position} googleMap={gmap}>
{m.icon}
</CustomMarkerNew>
);
})}
</>
) : null}
</Map>
This is how the CustomMarkerNew component looks
CustomMarkerNew.js
import React, { useState, useEffect, useCallback } from "react";
import ReactDOM from "react-dom";
const OverlayView = (props) => {
const { google } = window;
const { googleMap, position, children } = props;
const [latlng, setLatlng] = useState(position);
const [container, setContainer] = useState(document.createElement("div"));
const [overlay, setOverlay] = useState(undefined);
const initOverlay = useCallback(() => {
const overlayView = new google.maps.OverlayView();
overlayView.setMap(googleMap);
overlayView.getDraggable = () => true;
setOverlay(overlayView);
setLatlng(position);
google.maps.OverlayView.preventMapHitsFrom(container);
}, [googleMap, container]);
const onAdd = useCallback(() => {
// only draw marker if it has not been assigned the customMarker class
if (!container.classList.contains("customMarker")) {
container.classList.add("customMarker");
container.style.position = "absolute";
const panes = overlay.getPanes();
panes.overlayImage.appendChild(container);
}
overlay.setPosition();
}, [overlay]);
const draw = useCallback(() => {
container.style.position = "absolute";
container.draggable = true;
overlay.getPanes().floatPane.appendChild(container);
overlay.setPosition();
}, [overlay, container, latlng]);
const onRemove = useCallback(() => {
ReactDOM.unmountComponentAtNode(container);
setContainer(undefined);
overlay.setMap(null);
}, [container, overlay]);
const setPosition = (pos) => {
// update global latlng value with current position of marker
if (pos) setLatlng(pos);
const projection = overlay.getProjection();
if (!projection) return;
// convert latlng value to pixel equivalent
const point = projection.fromLatLngToDivPixel(latlng);
// set left and top values from values from point variable to place marker correctly on map
if (point && point.x && point.y) {
container.style.left = `${point.x}px`;
container.style.top = `${point.y}px`;
}
};
const bindFunctions = useCallback(() => {
overlay.onAdd = onAdd.bind(this);
overlay.draw = draw.bind(this);
overlay.onRemove = onRemove.bind(this);
overlay.setPosition = setPosition.bind(this);
overlay.getDraggable = () => true;
}, [overlay]);
useEffect(() => {
if (overlay) {
bindFunctions();
}
}, [overlay]);
useEffect(() => {
initOverlay();
}, []);
return (
(overlay &&
children &&
container &&
ReactDOM.createPortal(children, container)) ||
null
);
};
export default React.memo(OverlayView);
Here is a CodeSandbox as an example
Any help would be much appreciated.
I'm working on a simple implementation of a map using Google Maps API in a React project. I'm showing an info window to the user when they click a marker based on the state of the infoWindowStatus in the state object. If infoWindowStatus is true the info window displays.
If I mutate state directly and use a forceUpdate, the info window shows correctly. When I use setState, the info window does not show. (However, I can see that the infoWindowStatus DOES change from false to true when I console.log this.state.locations[myKey].infoWindowStatus.)
The problem I'm having is:
Using this.setState with update from immutability-helper is not re-rendering React Component.
import update from 'immutability-helper';
state = {
locations : [
{
position: {lat: 41.3029876, lng: -72.9191306},
title: "Pepe's Pizza",
mappy: '',
infoWindowStatus: false,
key : 0,
infoWindow :
{
content : "Loading...",
contentUrl : ""
}
}
]
}
// THIS DODGEY CODE WORKS
this.state.locations[myKey].infoWindowStatus = true;
this.forceUpdate()
//THIS CODE DOES NOT WORK
this.setState(
{ locations: update(this.state.locations, { [myKey] : {infoWindowStatus:
{$set:true}}})
}
);
The Entire Component as Requested is:
import React, { Component } from 'react';
import './css/App.css';
import './css/Custom.css';
import NeighborhoodMap from './neighborhoodMap';
import 'typeface-roboto';
import escapeRegEx from 'escape-string-regexp';
import update from 'immutability-helper';
class App extends Component {
state = {
locations : [
{
position: {lat: 41.3029876, lng: -72.9191306},
title: "Pepe's Pizza",
mappy: '',
infoWindowStatus: false,
key : 0,
infoWindow :
{
content : "Loading...",
contentUrl : ""
}
}
]
}
// TODO: separate data into external data file.
componentDidMount(){
this.setState({
filteredLocationsOnly : this.state.locations
})
}
// Launches Info Window on Google Map
showInfoWindowNow(locationSelected){
let myKey;
this.state.locations.filter( (location) =>{
if (locationSelected.name === location.title || locationSelected.title === location.title){
myKey = location.key;
return location
}
} );
this.updateInfoWindowContentAgain(myKey);
// // THIS CODE DOES NOT WORK AT ALL
// this.setState({ locations[myKey].infoWindowStatus : true })
// // THIS CODE DOESN'T SHOW THE INFO WINDOW
// console.log("Status: ", this.state.locations[myKey].infoWindowStatus);
// const tempLocations = [...this.state.locations];
// tempLocations[myKey] = { ...tempLocations[myKey], infoWindowStatus: true };
//
// this.setState(
// {
// locations: tempLocations
// }
// );
// console.log("Status Now: ", this.state.locations[myKey].infoWindowStatus);
// THIS DODGEY CODE WORKS
// https://stackoverflow.com/questions/51250518
this.state.locations[myKey].infoWindowStatus = true;
this.forceUpdate()
} //showInfoWindowNow
// Close Info Window on Google Map
closeInfoWindowNow(locationSelected){
this.forceUpdate()
}
// Update Content for Info Window
updateInfoWindowContentAgain(myKey){
return this.getInfoWindowContent(this.state.locations[myKey].title, myKey);
}
// Update Content for Info Window sub-function
getInfoWindowContent(searchTerm, myKey){
var nytAuthKey = "3d6801dab968446787ea71d5042ad8f7";
var myNewYorkTimesUrl = `https://api.nytimes.com/svc/search/v2/articlesearch.json?&api-key=${nytAuthKey}&q=${searchTerm}`
var contentForLocation;
var contentUrl;
let content = fetch(myNewYorkTimesUrl)
.then(response => response.json() )
.then(data => {
return addArticles(data);
}
)
.catch(error => requestError(error, 'articles'));
// add text from fetch request
function addArticles(data){
if (data.response && data.response.docs && data.response.docs.length > 1){
const articles = data.response.docs;
// var content, contentUrl;
let infoWindow = {};
articles.map(article => {
infoWindow.content = `${article.snippet}`;
infoWindow.contentUrl = `${article.web_url}`;
contentForLocation = `${article.snippet}`;
contentUrl = `${article.web_url}`;
return infoWindow;
});
}
} //addArticles
// Handle Errors
function requestError(error, part) {
console.log("Error: ", error);
}
content.then( content => {
this.state.locations[myKey].infoWindow.content = (contentForLocation);
this.state.locations[myKey].infoWindow.contentUrl = contentUrl;
this.forceUpdate()
}
)} // getInfoWindowContent
// end Nyt
filterLocations(query){
const match = new RegExp(escapeRegEx(query), 'i')
let showingLocations = this.state.locations.filter((location) => {
return match.test(location.title);
});
this.setState({
filteredLocationsOnly : showingLocations
})
} // end filterLocations
clearQuery = () => {
this.setState({query : ''});
}
updateQuery = (query) => {
this.setState({query : query.trim()})
this.filterLocations(query);
}
render() {
return (
<div className="App">
<NeighborhoodMap
menuOpen = {this.state.menuOpen}
locations = {this.state.locations}
filteredLocationsOnly = {this.state.filteredLocationsOnly}
query = {this.state.query}
updateQuery = { (query) => {
this.updateQuery(query)
}}
clearQuery = { () => {
this.clearQuery()
}}
filterLocations = { (query) => {
this.filterLocations(query)
}}
infoWindowStatus = {this.state.infoWindowStatus}
showInfoWindowNow = { (location) => {
this.showInfoWindowNow(location)
}}
closeInfoWindowNow = { (location) => {
this.closeInfoWindowNow(location)
}}
updateInfoWindowContentAgain = { (id) => {
this.updateInfoWindowContentAgain(id)
}}
infoWindow = {this.state.infoWindow}
/>
</div>
) // return
}// render
} // Component
export default App;
You could skip the immutability helper and instead copy your locations array, create a copy of locations[myKey], and overwrite infoWindowStatus:
const locations = [...this.state.locations];
locations[myKey] = { ...locations[myKey], infoWindowStatus: true };
this.setState({ locations });
Also, the infoWindowStatus you give to your NeighborhoodMap is from this.state.infoWindowStatus, but you store it in this.state.locations[0].infoWindowStatus.
We are building a React-Redux web app that will display multiple Three JS scenes. These scenes come in pairs, and each pair will have synchronized zooming. To facilitate that, we're storing camera data in the Redux store.
Here is our React class (take a deep breath, it's a little long for a SO question), which uses react-three-renderer to produce Three JS objects:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Vector3 } from 'three';
import React3 from 'react-three-renderer';
import ReferenceGrid from './ReferenceGridVisual';
import ResourceGroup from './resourceGroups/ResourceGroup';
import { initializeRayCastScene } from './viewportMath/RayCastScene';
import zoomCamera from './viewportMath/CameraZoom';
import { registerCamera, zoom } from './actions/cameraActions';
import { InitThreeJsDomEvents, UpdateDomCamera } from './domUtility/ThreeJSDom';
class ThreeJsScene extends Component {
constructor(props) {
super(props);
this.ZoomAmount = 150;
this.ZoomMaxCap = 1000;
this.ZoomMinCap = 6;
this.zoomPadding = 10;
this.minimumZoom = 45;
}
componentWillMount() {
initializeRayCastScene();
this.props.registerCamera(this.props.sceneName);
}
componentDidMount() {
// eslint-disable-next-line no-underscore-dangle
InitThreeJsDomEvents(this.camera, this.canvas._canvas);
}
onWheel = (event) => {
// eslint-disable-next-line
this.zoom(event.clientX, event.clientY, event.deltaY);
}
setCameraRef = (camera) => {
UpdateDomCamera(camera);
this.camera = camera;
}
zoom(screenPosX, screenPosY, zoomAmount) {
const size = {
width: this.props.width,
height: this.props.height,
};
const result = zoomCamera(screenPosX, screenPosY, zoomAmount, this.camera.position,
size, this.props.distance, this.camera, this.props.cameraType, this.ZoomMaxCap,
this.ZoomMinCap);
this.ZoomAmount = (result.ZoomAmount) ? result.ZoomAmount : this.ZoomAmount;
this.props.zoom(this.props.sceneName, result.distanceChangeFactor, result.newCameraPosition);
}
render() {
let position;
if (this.props.cameraPosition != null) {
position = new Vector3(
this.props.cameraPosition.x,
this.props.cameraPosition.y,
this.props.cameraPosition.z
);
} else {
position = new Vector3();
}
const left = -this.props.width / 2;
const right = this.props.width / 2;
const top = this.props.height / 2;
const bottom = -this.props.height / 2;
return (
<div
style={{ lineHeight: '0' }}
onWheel={this.onWheel}
>
<React3
width={this.props.width}
height={this.props.height}
mainCamera="camera"
antialias
pixelRatio={1}
ref={(canvas) => { this.canvas = canvas; }}
>
<scene ref={(scene) => { this.scene = scene; }}>
<orthographicCamera
name="camera"
left={left}
right={right}
top={top}
bottom={bottom}
near={0.01}
far={1400}
position={position}
ref={this.setCameraRef}
/>
<ambientLight
color={0xaaaaaa}
/>
<directionalLight
color={0xaaaaaa}
intensity={1.1}
position={new Vector3(3, 4, 10)}
lookAt={new Vector3(0, 0, 0)}
/>
<ReferenceGrid xActive yActive zActive={false} store={this.props.store} />
<ResourceGroup store={this.props.store}>
{this.props.children}
</ResourceGroup>
</scene>
</React3>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
const ownCamera = state.cameras.get(ownProps.sceneName);
if (ownCamera == null) {
console.log('own camera null');
return { cameraAvailable: false };
}
console.log('has own camera');
const cameraPosition = ownCamera.position;
const cameraType = ownCamera.type;
const distance = ownCamera.distance;
return {
cameraAvailable: true,
cameraPosition,
cameraType,
distance,
};
};
const mapDispatchToProps = dispatch => ({
registerCamera: (cameraName) => {
dispatch(registerCamera(cameraName));
},
zoom: (cameraName, factor, newCameraPosition) => {
dispatch(zoom(cameraName, factor, newCameraPosition));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(ThreeJsScene);
Additionally, for reference, here are the action creators:
export const registerCamera = cameraName => (dispatch) => {
dispatch({ type: 'REGISTER_CAMERA', newCameraName: cameraName });
};
export const zoom = (cameraName, factor, newCameraPosition) => (dispatch, getState) => {
const state = getState();
const zoomFactor = state.cameras.get(cameraName).distance * (1 - factor);
dispatch({ type: 'CAMERA_ZOOM', cameraName, factor: zoomFactor, newCameraPosition });
};
And the reducer:
import { Map } from 'immutable';
const defaultCameraProperties = {
distance: 150,
type: 'orthogonal',
position: { x: 0, y: 10, z: 50 },
rotation: { x: 0, y: 0, z: 0, w: 1 },
};
const initialState = Map();
export default (state = initialState, action) => {
switch (action.type) {
case 'REGISTER_CAMERA': {
const newCamera = {
...defaultCameraProperties,
...action.newCameraProperties,
};
return state.set(action.newCameraName, newCamera);
}
case 'CAMERA_ZOOM': {
const updatedDistance = action.factor;
const updatedCameraPosition = {
...state.get(action.cameraName).position,
...action.newCameraPosition,
};
const updatedCamera = {
...state.get(action.cameraName),
position: updatedCameraPosition,
distance: updatedDistance,
};
return state.set(action.cameraName, updatedCamera);
}
default: {
return state;
}
}
};
The challenge is in the zoom function in the React class, the React props are not what I would expect, and therefore zooming is failing. Here is a summary of the sequence of relevant events as I understand them:
componentWillMount is called, which dispatches the REGISTER_CAMERA method. (We do this rather than having camera data by default in the store because these pairs of scenes are generated dynamically - there is not a static number of them.)
The React render method is called.
The React render method is called again since the REGISTER_CAMERA action has now modified the store and we have new props - the camera related props are now available.
I trigger zoom with my mouse wheel. The onWheel handler calls the zoom function, but breakpointing in that method reveals that the camera related props - like this.props.cameraType - are undefined. The React props appear as they do in 2. (zoomCamera does some calculations. Since these properties are unavailable, zooming fails.)
I can't figure out why this is. My suspicion is I'm misunderstanding something about what this context is bound to the zoom method.
In short my question is why are my props not up to date and how can I make the updated version available to the zoom function?
Turns out it was an error with hot module reloading. Running our build cold does not exhibit the issue.