I am using Redux form for form in React.js and my form was and I have a custom google map component I want to bind lat and long to my form
form
import React from 'react';
import { Field, reduxForm } from 'redux-form';
const SimpleForm = props => {
const { handleSubmit, pristine, reset, submitting } = props;
return (
<form onSubmit={handleSubmit}>
<div className="position-relative form-group">
<label>First Name</label>
<div>
<Field
name="firstName"
component="input"
type="text"
placeholder="First Name"
className="form-control"
/>
</div>
</div>
<Field name = 'eventLocation'
component = {MyParentComponentWrapper} />
</form>
);
};
export default reduxForm({
form: 'simple', // a unique identifier for this form
})(SimpleForm);
and my MyParentComponentWrapper code was
import React from 'react';
import { compose, withProps, lifecycle } from 'recompose';
import { withScriptjs, withGoogleMap, GoogleMap, Marker } from 'react-google-maps';
const MyMapComponent = compose(
withProps({
googleMapURL:
'https://maps.googleapis.com/maps/api/js?key=AIzaSyCYSleVFeEf3RR8NlBy2_PzHECzPFFdEP0&libraries=geometry,drawing,places',
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
lifecycle({
componentWillMount() {
const refs = {};
this.setState({
position: null,
onMarkerMounted: ref => {
refs.marker = ref;
},
onPositionChanged: () => {
const position = refs.marker.getPosition();
console.log(position.toString());
},
});
},
}),
withScriptjs,
withGoogleMap
)(props => (
<GoogleMap defaultZoom={8} defaultCenter={{ lat: -34.397, lng: 150.644 }}>
{props.isMarkerShown && (
<Marker
position={{ lat: -34.397, lng: 150.644 }}
draggable={true}
ref={props.onMarkerMounted}
onPositionChanged={props.onPositionChanged}
/>
)}
</GoogleMap>
));
class MyParentComponentWrapper extends React.PureComponent {
state = {
isMarkerShown: false,
};
render() {
return (
<div>
<MyMapComponent isMarkerShown={true} />
</div>
);
}
}
export default MyParentComponentWrapper;
this component will console.log the lat and long values when user drag the marker
How to pass the console.log value to redux-form?
Can anyone please suggest a way to do so?
Here is a codesandbox of your app using a redux-form. Notice that after setting up the form in latLngForm.js, I use connect in your map container to dispatch reduxForm's change action when your marker is moved. This is what updates the store.
I also pass position in as a prop to <MyMapComponent /> to set the position of your marker. This means that your marker's position is always based off of the form's values, and that moving the map's markers manually changes the form's value. This will allow you to set the position manually via the fields, or by dragging and dropping the marker.
mapStateToProps in the <MyMapComponent /> is the important piece here. redux-form automatically stores the values in the state for us, this is where we retrieve it.
Notice these lines at the top of the file:
import { change, formValueSelector } from "redux-form";
...
const formSelector = formValueSelector("form");
This sets up our form selector. "form" being the identifier for the form. Now, to retrieve these values from our state, we do:
const mapStateToProps = (state) => ({
position: {
lat: formSelector(state, 'lat'),
lng: formSelector(state, 'lng') // The key here is the name you passed into the field.
}
});
Then we use connect to actually connect the component to the store:
connect(mapStateToProps)(MyMapComponent);
Redux does its magic and now our fields are available via this.props.position in our component!
Here is my working solution, forked from your sandbox.
I just added a callback in your MyParentComponentWrapper component which is fired every time the marker position changes. In your form, you listen to the callback and set the position field with the value received from MyParentComponentWrapper.
I like to keep components as simpler as possible and not to connect them with redux state via connect. Just think of your MyParentComponentWrapper as an input field which only fires change event every time its value changes without being aware of how/where this value will be used.
In this case MyParentComponentWrapper just show a marker and invokes a callback every time marker position changes. It's the form component the one in charge of handling this position in the proper way.
Here's the how I changed your form render with the callback:
<MyParentComponentWrapper setPosition={onPositionSet} />
and here's the callback in your form component:
const onPositionSet = (position) => {
props.change("position", position);
}
where change function has been added to your component props as you wrapped it in a redux-form
For functional components you can do this:
useEffect(()=>{
dispatch(change("travellerDetailsForm", "dob", "DD/MM/YYYY"));
},[])
or you can do it in the change handler with the event too like this-
const panNameChangeHandler = (e) => {
e.preventDefault();
const panName = e.target.value.toUpperCase();
dispatch(change("travellerDetailsForm", "name", panName));
};
Related
I have a component that implements a simle Formik form. The component also has listeners for an event generated by clicking on a LeafletJS map. I would like to be able to update the lat and lng fields whenever the event handler is called so that they can optionally be filled by clicking on the map instead of entering values directly.
import React, { useEffect } from 'react';
import { Formik, Form, Field } from 'formik';
const FormComponent = () => {
const onMapClick = e => {
console.log(e.detail.lat, e.detail.lng);
};
useEffect(() => {
document.addEventListener('map:click', onMapClick);
return () => {
document.removeEventListener('map:click', onMapClick);
};
}, []);
return (
<Formik
initialValues={{ username: 'guest', lat: 50.447243, lng: 30.524933 }}
onSubmit={values => { console.log(values); }}
>
{() => (
<Form id="test">
<Field type="text" name="username" />
<Field type="text" name="lat" />
<Field type="text" name="lng" />
<button type="submit">
Submit
</button>
</Form>
)}
</Formik>
);
};
export default FormComponent;
I have come across the Formik setFieldValue function but this only seems to be accessible from within the form. Likewise I cannot use UseEffect within the form function.
I have had some success by changing the intial values and using enableReinitialize but this resets other fields unless I keep track of all fields and update the initial values on each change.
What would be the recommended way of acheiving this? I should add that the code is a trimmed down example. The real form has way more fields.
This is how I would make this.
Imagine that you have component with form, component with map and some parent component that contain both of them.
I would use some useState in parent component that will handle change of selected lat and lng and provide this data to my form.
Then in form I'll use useEffect, so whenever those data is changed it will setFieldValue in form.
All this stuff will look like that (abstract example):
Parent component
const [coords, setCoords] = useState(null)
return (
<>
<Form coords={coords} />
<Map onChange={setCoords} />
</>
)
Form
useEffect(() => {
if(coords !== null) {
setFieldValue("lat", coords.lat)
setFieldValue("lng", coords.lng)
}
}, [coords])
This should work because setFieldValue can set values for fields in every moment of time, even after initialization. I'm not sure what problems have you had with useEffect and setFieldValue, so if my answer didn't helped you, provide some more code examples
The Google Maps API library I am using for this is https://github.com/JustFly1984/react-google-maps-api
What I have been asked to create is a map with multiple markers, each of which can be clicked on to open an InfoWindow centred on that marker. Much like the AirBnB search page, with the map on one side results on the other.
However, I seem to only be able to have an InfoWindow that is open at the loading of the page and then can't be opened again.
My research so far has generally shown that I would need to completely redo this component as a class instead. Although I don't really understand the class based model of components, so ideally I wouldn't have to mess around with that.
Is there a way to make the desired effect without reworking my program entirely?
This is the code for the component, in case it is relevant
import styles from '../../styles/Home.module.css';
import { Marker, LoadScriptNext, GoogleMap, InfoWindow } from '#react-google-maps/api';
import { Fragment, React } from 'react';
import { Typography } from '#material-ui/core';
export default function MapPanel(props) {
return (
<Fragment>
<LoadScriptNext id='google-map-example' googleMapsApiKey="MYAPIKEY">
<GoogleMap mapContainerStyle={{ width: "100%", height: "800px" }}
id='google-map'
center={props.centre}
heading="0"
zoom={12}>
{addMarkers(props.markerList)}
</GoogleMap>
</LoadScriptNext>
</Fragment>
);
}
function addMarkers(markerList) {
if (markerList != undefined) {
return (markerList.map((marker, index) => makeMarker(marker, index)))
}
}
function makeMarker(marker, index) {
const handleClick = () => {
console.log(`${marker.title} marker clicked`);
}
return (
<Marker id={index} position={marker.latLong} title={marker.title} onClick={handleClick}>
<InfoWindow id='Test' position={marker.latLong}>
<Typography variant='body2'>{marker.text}</Typography>
</InfoWindow>
</Marker>
);
}
To achieve your goal you would want to use states in your application. But since you want to deal with it using Function components, you would need to make use of react hooks to use states.
Here is a working sample for your reference: https://stackblitz.com/edit/marker-with-infowindow-react-api-o6357y
import ReactDOM from "react-dom";
import React from "react";
import {
GoogleMap,
Marker,
InfoWindow,
LoadScript
} from "#react-google-maps/api";
import data from "./data.json";
const { useState } = React;
const containerStyle = {
width: "400px",
height: "400px"
};
const center = { lat: 40.712775, lng: -74.005973 };
function Map() {
const [infoWindowID, setInfoWindowID] = useState("");
let markers;
if (data !== null) {
markers = data.map((location, i) => {
const marker = { lat: location.lat, lng: location.lng };
const index = i + 1;
return (
<Marker
key={index}
position={marker}
label={index.toString()}
onClick={() => {
setInfoWindowID(index);
}}
>
{infoWindowID === index && (
<InfoWindow>
<span>Something {index}</span>
</InfoWindow>
)}
</Marker>
);
});
}
return (
<LoadScript googleMapsApiKey="YOUR_API_KEY">
<GoogleMap mapContainerStyle={containerStyle} center={center} zoom={10}>
{markers}
</GoogleMap>
</LoadScript>
);
}
export default React.memo(Map);
I am creating a map application that consists of a map (react-map-gl) showing GeoJSON markers and a simple table that lists the properties of the same markers.
Both the map component and the table component are direct children of a common component (the main App component). When clicking a marker in the table, I want the map component to zoom to this marker.
For the map to start a transition, I need to set its state.viewport like shown in this example:
https://visgl.github.io/react-map-gl/docs/advanced/viewport-transition
If you move the "New York City" button in that example to a sister component instead, you have my case exactly.
How do I send a map transition request from a sister component to the map component in the "React way"?
The React way is to have a common parent that will handle all state related actions, that is called lifting your state up. The idea is to always keep a single source of truth, let it go down the tree via props and use function to update the state.
In your case, a single exemple could look like this:
const defaultViewport = viewport: {
width: 800,
height: 600,
longitude: -122.45,
latitude: 37.78,
zoom: 14,
}
// The app will handle the viewport and related update
const App = () => {
const [viewport, setViewport]= useState(defaultViewport);
const handleTransition = (transitionViewport) => {
setViewport({
...viewport,
...transitionViewport
});
}
const handleViewportChange = (newViewport) => {
setViewport(newViewport);
}
return (
<div>
<Map
viewport={viewport}
onViewportChange={handleViewportChange}
/>
<Menu
onTransition={handleTransition}
/>
</div>
);
}
// The map only need to be aware of the viewport and how to change it
const Map = ({ viewport, onViewportChange }) => {
return (
<ReactMapGL
{...viewport}
onViewportChange={onViewportChange}
/>
);
}
// The menu only need to know how to request a transition
const Menu = ({onTransition}) => {
const goToNYC = () => {
const viewport = {
longitude: -74.1,
latitude: 40.7,
zoom: 14,
transitionDuration: 5000,
transitionInterpolator: new FlyToInterpolator(),
transitionEasing: d3.easeCubic
};
onTransition(viewport);
}
return (
<div>
<button onClick={goToNYC}>New York City</button>
<div>
);
}
You'll need a parent component that will handle the state changes and then send the state and handler as props to the child components.
For example below, you have a parent component called MyApp which has two child components, Map which contains the map and NYCButton which contains the button that triggers the state change.
The MyApp component...
import React, { useState } from "react";
import { FlyToInterpolator } from "react-map-gl";
import d3 from "d3-ease";
import { Map } from "./Map";
import { NYCButton } from "./NYCButton";
const MyApp = () => {
const [viewport, setViewport] = useState({
width: 800,
height: 600,
longitude: -122.45,
latitude: 37.78,
zoom: 14
});
const goToNYC = () => {
setViewport({
...viewport,
longitude: -74.1,
latitude: 40.7,
zoom: 14,
transitionDuration: 5000,
transitionInterpolator: new FlyToInterpolator(),
transitionEasing: d3.easeCubic
});
};
return (
<div className="MyApp">
<Map viewport={viewport} onViewportChange={setViewport} />
<NYCButton goToNYC={goToNYC} />
</div>
);
};
export { MyApp };
The Map component...
mport React from "react";
import ReactMapGL from "react-map-gl";
const Map = ({ viewport, onViewportChange }) => (
<div>
<ReactMapGL {...viewport} onViewportChange={onViewportChange} />
</div>
);
export { Map };
The NYCButton component...
import React from "react";
const NYCButton = ({ goToNYC }) => (
<div>
<button onClick={goToNYC}>New York City</button>
</div>
);
export { NYCButton };
Usig google-map-react, how do I zoom on a customized marker after I click on it?
I am able to click on the marker but it is not zooming after the click.
Below a snippet of code for the most important parts:
GoogleMap.js
import React, { Component } from 'react';
import GoogleMapReact from 'google-map-react';
const markerStyle = {
position: 'absolute'
};
function CustomMarker({ lat, lng, onMarkerClick }) {
return (
<div onClick={onMarkerClick} lat={lat} lng={lng}>
<img style={markerStyle} src={icon} alt="icon" />
</div>
);
}
function MapClick({ center, zoom, data }) {
function handleMarkerClick() {
console.log('Click');
}
}
class BoatMap extends Component {
constructor(props) {
super(props);
this.state = {
buttonEnabled: true,
buttonClickedAt: null,
progress: 0,
ships: [],
type: 'All'
};
}
// Operations.......
render() {
return (
<div className="google-map">
<GoogleMapReact
bootstrapURLKeys={{ key: 'My_KEY' }}
center={{
lat: 42.4,
lng: -71.1
}}
zoom={8}
defaultZoom={zoom}
defaultCenter={center}
>
{/* Rendering all the markers here */}
{this.state.ships.map((ship) => (
<Ship ship={ship} key={ship.CALLSIGN} lat={ship.LATITUDE} lng={ship.LONGITUDE} />
))}
{/* Trying to focus on the marker after clicking on it */}
{data.map((item, idx) => {
return <CustomMarker onMarkerClick={handleMarkerClick} key={idx} lat={item.lat} lng={item.lng} />
})}
</GoogleMapReact>
</div>
);
}
}
ShipTracker.js is where I detect the correct click event:
const Ship = ({ ship }) => {
const shipName = ship.NAME;
const company = shipCompanyMap[shipName];
function handleMarkerClick() {
console.log('marker clicked');
}
const shipImage = companyImageMap[company];
return (
<div onClick={handleMarkerClick}>
{/* Render shipImage image */}
<img src={shipImage} alt="Logo" />
</div>
);
};
export { Ship };
What I have done so far:
what I tried so far:
1) I came across this source and that was useful to understand how to create a marker but unfortunately I was not able to solve the problem.
2) In my case I use google-map-react instead of google-map. I know that the procedure is similar, but for some reasons I might be missing something.
3) After digging more into the problem I came across this source and it was very useful but still I could not fix the problem.
I'm not sure but you can try to get instance of map throw ref
like
<GoogleMapReact ref={this.map}
and then use it in handler
function handleMarkerClick(marker) {
this.map.panTo(marker.coordinates);
}
I want to pass coordinates from a class to another one. I am retrieving coordinates from an api. The code looks like the following:
import React, { Component } from 'react';
class Test3 extends Component{
state = {
loading: true,
coordinates: null,
}
async componentDidMount(){
const url = "https://ttr.vsbbn.nl:4000/gps_history?team_id=10";
const response = await fetch(url);
const data = await response.json();
this.setState({coordinates: data, loading: false });
}
render(){
const { loading, coordinates } = this.state
return(
<div>
{loading || !coordinates ? (
<div>loading...</div>
) : (
<div>
{coordinates.map((coordinate, index) => {
return (
<div key={index}>
<p>Longitute: {coordinate.lon}</p>
<p>Latitude: {coordinate.lat}</p>
<p>Time: {coordinate.timestamp}</p>
<p>...............</p>
</div>
)
})}
</div>
)}
</div>
)
}
}
export default Test3;
And i want to pass the coordinates to the class here below. I already have the path hardcoded in an array. The api coordinates supposed to be in the path array, thats my goal. The code is here:
import React from 'react';
import {
GoogleMap,
withScriptjs,
withGoogleMap,
Marker,
Polyline}
from 'react-google-maps';
import '../App.css';
import { Link } from "react-router-dom";
class Map extends React.Component {
path = [
{ lat: 18.558908, lng: -68.389916 },
{ lat: 18.558853, lng: -68.389922 },
{ lat: 18.558375, lng: -68.389729 },
{ lat: 18.558032, lng: -68.389182 },
{ lat: 18.55805, lng: -68.388613 },
{ lat: 18.558256, lng: -68.388213 },
{ lat: 18.558744, lng: -68.387929 }
];
render = () => {
return (
<GoogleMap
defaultZoom={16}
defaultCenter={{ lat: 18.559008, lng: -68.388881 }}
>
<Polyline path={this.path} options={{ strokeColor: "#FF0000 " }} />
<Marker position={this.path[this.path.length - 1]} />
</GoogleMap>
)
}
}
const WrappedMap = withScriptjs(withGoogleMap(Map));
export default() => (
<div>
<br/>
<b>Klik <Link to="/" className="btn btn-primary">hier</Link> om terug te gaan naar teamoverzicht</b>
<br/>
<br/>
<h3>Live kaart</h3>
<p>
Teamnaam: Hogeschool Rotterdam 1 <br/>
Live:<span class="dot"></span><br/>
Actuele snelheid: 8 km per uur <br/>
Gemiddelde snelheid: 7 km per uur <br/>
Verwachte aankomsttijd Rotterdam: 12 mei 17.59 <br/>
</p>
<div style={{width: '50vw', height: '50vh'}}>
<WrappedMap googleMapURL = {'https://maps.googleapis.com/maps/api/js?key=AIzaSyB9w0T_VMezCy1AaqxXpRie9ChrbVCt1O4&v=3.exp&libraries=geometry,drawing,places'}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
</div>
)
Any idea how to do this? My goal is showing real time the location of a person.
Data passing in React is unidirectional, it means that only a parent can pass props to its child. So if you can nest your component "Map" into "Test3" that will solve it. If you can't do that, then you need to use Redux, which is state management library, that will handle complex data passing between components.
PS: there is way to pass data from a child to its parent, but its very limited (I can send you a tuto if you're interested)
According to this documentation, A higher-order component (HOC) is an advanced technique in React for reusing component logic. HOCs are not part of the React API, per se. They are a pattern that emerges from React’s compositional nature
so after you read this doc we are here to answer all of your questions about it.
you need to save the coordinates in an ordered way (to your state) that you could pass it to the children components (in your case <Map />).
in the componentDidMount of your <Test3 /> component try this:
async componentDidMount(){
const url = "https://ttr.vsbbn.nl:4000/gps_history?team_id=10";
const response = await fetch(url);
const data = await response.json();
const coordinatesList = []
data.forEach(coor => {
coordinatesList.push({ lat: coor.lat, lng: coor.lng })
})
this.setState({coordinates: coordinatesList, loading: false });
}
this would result something like below:
coordinatesList: [
{ lat: someNumber, lng: someNumber },
{ lat: someNumber, lng: someNumber },
{ lat: someNumber, lng: someNumber },
...
]
after that inside of render function you can pass the coordinatesList state to your child component and use it, something like
<Map coorList={coordinatesList} />
no need to loop over coordinates! you need to do what ever you want inside the map component wityh all of your coordinates. (as a side note i'm thinking that you try to create markers of those coordinates, so you need to loop over coorList props that you've been received from your parent and return a <Marker /> component of each of that coordinates )
Another option could be use use react context/provider, which would serve as a container for your data and also any functions to update the data. Your context could be accessed from both components and gives you the same single source of data that redux gives you, without all the boilerplate code that goes with redux.
Create a provider component and a use context hook
import React, { createContext, useContext, useState, useEffect } from 'react'
const Context = createContext({})
// This is your provider
const MapDataProvider = props => {
const [mapData, setMapData] = useState([...your original coordinates])
useEffect(() => {
...your call to google
.then(data, setMapData(current => [...current, data])
}, [])
return (
<Context.Provider value={{coordinates: mapData}}>
{props.children}
</Context.Provider>
)
}
// This is your hook
const useMapDataContext = () => useContext(Context)
export { MapDataProvider, useMapDataContext }
Wrap the exported Provider around your root component
import { MapDataProvider } from './yourcontextfile.js'
const RootComponent = () => (
<MapDataProvider>
<Test3/>
<WrappedMap/>
...whatever, the structure here is not important
</MapDataProvider>
)
Then in any of your components where you need to access the data, you use the exported hook useMapDataContext
import { useMapDataContext } from './yourcontextfile.js'
const Test3 = () => {
// This is deconstructed from the value prop on the provider component
const { coordinates } = useMapDataContext()
... the rest of your component
}
I haven't compiled or tested any of this, it's just a quick example of how you could use a context component to have your data in one place, accessible from any other component, as long as it's inside the <MapDataProvider> component.
I'm not saying it's the right way to do it, it's just another way.