• No results found

Adding More Details to Markers

In document (Full Stack React)-BuildingYelp.pdf (Page 94-101)

}

Container.contextTypes = {

router: React.PropTypes.object }

Now, inside the onMarkerClick() function we can get access to the push() method from the router context and call it with the destination route:

export class Container extends React.Component { // ...

onMarkerClick(item) {

const {place} = item; // place prop const {push} = this.context.router;

push(`/map/detail/${place.place_id}`) }

// ...

}

Now, clicking on a marker will push us to a new route, the /map/detail/${place.place_id}

route.

Adding More Details to Markers

Let’s create the /detail/:placeId route. When our user navigates to a URL like /detail/abc123, instead of /map, we’ll want to show them the details for the place with the id of abc123. This scheme allows us to copy and paste the URL so the user can share it around and expect to see the same result every single time.

To create the new route, let’s modify the routes.js file so that we have this route. In our src/views/Main/routes.js file, let’s add:

export const makeMainRoutes = () => {

return (

<Route path="" component={Container}>

<Route path="map" component={Map} />

<Route path="detail/:placeId"

component={Detail} />

</Route>

) }

React router takes the final part of the URL and treats it as a variable route. That is to say if the user visits a url such as: /detail/abc123, it will match our new route and the :placeId will be passed through as props to our Detail component as params. We’ll come back to this in a moment.

Let’s create the Detail component and it’s css module (just like we did previously for the <Map />

component):

$ mkdir src/views/Main/Detail

$ touch src/views/Main/Detail/{styles.module.css,Detail.js}

The <Detail /> component is a single component that is responsible for showing the data

associated with a place. In order to handle finding more details about the place, we can call another Google API that directly gives us details about one specific place.

Let’s create this API handler in our src/utils/googleApiHelpers.js (as we have done with the nearbySearch()). Let’s add the following request at the end of the file:

export function getDetails(google, map, placeId) { return new Promise((resolve, reject) => {

const service = new google.maps.places.PlacesService(map);

const request = {placeId}

service.getDetails(request, (place, status) => {

if (status !== google.maps.places.PlacesServiceStatus.OK) { return reject(status);

Let’s create the <Detail /> component, making sure to import our new helper function (which we’ll use shortly). In the src/views/Main/Detail/Detail.js, let’s create the component:

import React, { PropTypes as T } from 'react' import {getDetails} from 'utils/googleApiHelpers' import styles from './styles.module.css'

export class Detail extends React.Component { constructor(props, context) {

super(props, context)

this.state = { loading: true, place: {}, location: {}

} }

getDetails(map) {}

// ...

render() { return (

<div className={styles.details}></div>

) } }

export default Detail

The <Detail /> component is a stateful component as we’ll need to hold on to the result of an API fetch to the getDetails() request. In the constructor, we’ve set the state to hold on to a few values, including the loading state of the request.

We have to handle two cases for when the <Detail /> component mounts or updates in the view.

1. The first case is when the <Detail /> mounts initially, we’ll want to make a request to fetch more details about the place identified by the :placeId.

2. The second is when the map component updates or the placeId changes.

In either case, we’ll need a common method for getting details. Let’s update our getDetails() method inside the <Detail /> component that we’ll call our helper method of the same name. First, let’s get the placeId from the URL (passed to our component through this.props.params):

import {getDetails} from 'utils/googleApiHelpers'

export class Detail extends React.Component { getDetails(map) {

// the placeId comes from the URL, passed into

With the placeId, we can call our helper method and store the result from the returned promise:

import {getDetails} from 'utils/googleApiHelpers'

export class Detail extends React.Component { getDetails(map) {

// the placeId comes from the URL, passed into

// this component through params

Although it looks like quite a bit, the method is straight-forward. We’re setting the state as loading (so we can show the loading state in the view) and then calling the method. When it comes back

successfully, we’ll update the state with the place/location and update the loading state.

We’re storing the location as a custom state object to standardize the location, rather than creating the object in the render() function.

Back to the two states, the first one is easy to handle. When the componentDidMount(), we can check to see if we have a map available in the props (we won’t have a map prop if the <Map /> has yet to load – it’s asynchronous, after all) and call our getDetails() method.

In the src/views/Main/Detail/Detail.js, let’s add the componentDidMount() function:

// ...

export class Detail extends React.Component { componentDidMount() {

if (this.props.map) {

this.getDetails(this.props.map);

} }

getDetails(map) { // ...

} }

The second case is relatively straight-forward as well. The component will update when the props update, which in our case might happen when the map is loading or the placeId changes in the URL.

If either one of those are true, we’ll call through to our getDetails() method:

// ...

export class Detail extends React.Component { componentDidUpdate(prevProps) {

if (this.props.map && // make sure we have a map (prevProps.map !== this.props.map ||

prevProps.params.placeId !== this.props.params.placeId)) { this.getDetails(this.props.map);

} }

getDetails(map) { // ...

} }

With that, we’ll have the place details in the this.state of the <Details /> component ready for rendering.

If we’re in a loading state (i.e. this.state.loading is true), we’ll show the user that we’re loading the page. We’ll set this up simply in the render() function in our <Details /> component:

export class Detail extends React.Component { // ...

render() {

if (this.state.loading) {

return (<div className={styles.wrapper})>

Loading...

Let’s show the place’s name, which we now have in the this.state.place:

export class Detail extends React.Component { // ...

render() {

if (this.state.loading) {

return (<div className={styles.wrapper})>

Loading...

</div>);

}

// We're no longer loading when we get here

const {place} = this.state;

return (

<div className={styles.wrapper}>

<h2>{place.name}</h2>

</div>

) } }

Before we move on, let’s add a small bit of style to the <Detail /> component. Mostly for

demonstration purposes as well as making our app responsive. Before we get there, let’s wrap our

<h2> element in the header class so we can modify the style and it’s container:

export class Detail extends React.Component { // ...

render() {

if (this.state.loading) {

return (<div className={styles.wrapper})>

Loading...

</div>);

}

// We're no longer loading when we get here const {place} = this.state;

return (

<div className={styles.wrapper}>

<div className={styles.header}>

<h2>{place.name}</h2>

</div>

</div>

) } }

Back in our src/views/Main/Detail/styles.module.css, we can add the .header{} CSS class definition to give it some definition. Let’s increase the font-size and add some padding around the title to make it stand out more:

.header {

padding: 0 25px;

h2 {

font-size: 1.5em;

} }

Remember, we can use the same padding across the entire app for consistency, so rather than hard-code the padding in the header, let’s use the globally defined variable --padding. First, we’ll need to import the base style and then use the var() syntax to apply the --padding:

@import url("../../../styles/base.css");

.header {

padding: 0 var(--padding);

h2 {

font-size: 1.5em;

} }

Although this style addition isn’t incredibly impressive, we now have confirmed the css module is hooked up to our <Detail /> component.

The Google Places API gives us back an interesting object with all sorts of fun goodies included, such as photos. Let’s get a photo panel showing the inside of the cafe (usually) that are handed back by API.

In document (Full Stack React)-BuildingYelp.pdf (Page 94-101)

Related documents