React/Redux newbie here. I have a form input that allows a user to enter a doctor issue. It returns a list of doctors from the server via Redux action, and displays the doctor list and a marker for each doctor's location on the map (react-google-maps).
When I click submit, the list of doctors for the correct issue displays, the map is there, but with no markers. I can get the markers on the map to display ONLY after submitting the form, THEN clicking on a doctor from the list to display their details.
Want: Enter a doctor issue and render both the list of doctors and markers on the map when the user clicks submit. Then, select a doctor to see their details page (that's another question, routing to dynamic a detail page).
I think, I need to use a life-cycle method but not sure how to implement it. Or, is there a better way to handle this with Redux?
Doctor component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import DoctorSearchForm from '../../containers/doctors/DoctorSearchForm';
import DoctorList from './DoctorList';
import Map from '../maps/Map';
class Doctors extends Component {
constructor(props) {
super(props);
this.state = {
markers: [],
isMarkerShown: false
}
}
componentDidMount() {
this.getMarkers();
}
getMarkers = () => {
let practices = this.props.doctors.map(function(doctor, index) {
return {
title: doctor.profile.first_name + ' ' + doctor.profile.last_name,
location: {
lat: doctor.practices[0].visit_address.lat,
lng: doctor.practices[0].visit_address.lon
}
}
});
this.setState({ markers: practices, isMarkerShown: true });
}
render() {
const { doctors, match } = this.props;
return (
<div>
<DoctorSearchForm getMarkers={this.getMarkers} />
<div className="row">
<div className="col-md-4">
<DoctorList doctors={doctors} match={match} />
</div>
<div className="col-md-8">
<Map
isMarkerShown={this.state.isMarkerShown}
center={{ lat: 45.6318,lng: -122.6716 }}
zoom={12}
markers={this.state.markers}
/>
</div>
</div>
</div>
);
}
}
Doctors.propTypes = {
doctors: PropTypes.array.isRequired,
match: PropTypes.object.isRequired
}
export default Doctors;
DoctorList component:
import React from "react";
import { Route } from 'react-router-dom';
import DoctorItem from './DoctorItem';
import DoctorView from './DoctorView';
class DoctorList extends React.Component {
render() {
const { doctors, match } = this.props;
const linkList = doctors.map((doctor, index) => {
return (
<DoctorItem doctor={doctor} match={match} key={index} />
);
});
return (
<div>
<h3>DoctorList</h3>
<ul>{linkList}</ul>
<Route path={`${match.url}/:name`}
render={ (props) => <DoctorView data= {this.props.doctors} {...props} />}
/>
</div>
);
}
}
export default DoctorList;
DoctorItem component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link, Route } from 'react-router-dom';
import DoctorView from './DoctorView';
const DoctorItem = (props) => {
const { doctor, match } = props;
return (
<li>
<Link to={{ pathname: `${match.url}/${doctor.profile.first_name}-${doctor.profile.last_name}` }}>
{doctor.profile.first_name} {doctor.profile.last_name}
</Link>
</li>
)
}
DoctorItem.propTypes = {
doctor: PropTypes.object.isRequired,
};
export default DoctorItem;
DoctorView component:
import React from 'react';
const DoctorView = ({ data, match }) => {
const doctor = data.find(p => `${p.profile.first_name}-${p.profile.last_name}` === match.params.name);
let doctorData;
if (doctor) {
const mappedSpecialties = Object.entries(doctor.specialties).map(([index, specialty]) => {
return <li key={index} id={index}>{specialty.description}</li>;
});
doctorData =
<div>
<h5><strong>{doctor.profile.first_name} {doctor.profile.last_name}</strong> - {doctor.profile.title}</h5>
<img src={doctor.profile.image_url} alt={"Dr." + doctor.profile.first_name + " " + doctor.profile.last_name} />
<ul>{mappedSpecialties}</ul>
<p>{doctor.profile.bio}</p>
</div>;
}
return (
<div>
{doctorData}
</div>
)
}
export default DoctorView;
Map component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withScriptjs, withGoogleMap, GoogleMap, Marker } from 'react-google-maps';
import { compose, withProps } from "recompose";
export default Map = compose(
withProps({
googleMapURL:
"https://maps.googleapis.com/maps/api/js?key={MY_KEY}&v=3.exp&libraries=geometry,drawing,places",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />
}),
withScriptjs,
withGoogleMap
)(props => (
<GoogleMap defaultZoom={9} defaultCenter={{ lat: 45.6318,lng: -122.6716 }}>
{props.markers.map((doctor, index) => {
const marker = {
position: { lat: doctor.location.lat, lng: doctor.location.lng },
title: doctor.title
}
return <Marker key={index} {...marker} />;
})}
</GoogleMap>
));
I've spent several days trying and searching for answers but no luck. Any help would be greatly appreciated!
Just like you calculate the markers when the component mounts, you need to recalculate your markers when you receive new props:
componentWillReceiveProps(nextProps) {
this.getMarkers(nextProps);
}
This will require you to change your getMarkers
signature a bit so that it can accept an argument and use that instead of this.props
in your map
operation:
getMarkers = (props = this.props) => {
let practices = props.doctors.map(function(doctor, index) {
return {
title: doctor.profile.first_name + ' ' + doctor.profile.last_name,
location: {
lat: doctor.practices[0].visit_address.lat,
lng: doctor.practices[0].visit_address.lon
}
}
});
this.setState({ markers: practices, isMarkerShown: true });
}
Assuming you are calling getMarkers()
in your DoctorSearchForm
component, you can remove that since it will automatically update the markers when receiving new props -- or you could bypass state altogether and just calculate it on the fly in render
based on the incoming props
.