26

Building a small react app that passes the geolocation (determined by the browser to a child component as props).

The first component: App.jsx

import React, {Component} from 'react';

import DateTime from './components/dateTime/_dateTime.jsx';
import Weather from './components/weather/_weather.jsx';
import Welcome from './components/welcome/_welcome.jsx';

require ('../sass/index.scss');

export default class App extends Component {

  constructor() {
    super();
    this.state = {
      latitude: '',
      longitude: ''
    };
    this.showPosition = this.showPosition.bind(this);
  }

  startApp () {
    this.getLocation();
  }

  getLocation() {
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(this.showPosition);
    } else {
        console.log("Geolocation is not supported by this browser.");
    }
  }

  showPosition(position) {
    this.setState({
        latitude: position.coords.latitude,
        longitude: position.coords.longitude
    })
  }

  componentWillMount () {
    this.startApp();
  }

  render() {
    return (
        <div className="container">
            <div className="header-container">
                <Weather latitude={ this.state.latitude } longitude={ this.state.longitude } />
            <DateTime />
            </div>
            <div className="welcome-container">
                <Welcome name="Name" />
            </div>
      </div>
    );
  }
}

This component determines the location, saves the latitude and longitude to state and passes this information via props to the Weather.jsx component, which is working as you can see in the below image:

enter image description here

And in the weather.jsx component I try and access these props and get either undefined or an empty object.

import React, {Component} from 'react';
import Fetch from 'react-fetch';

export default class Weather extends Component {

    constructor(props) {
        super(props);
        this.state = {
          forecast: {},
          main: {},
          weather: {},
        };
        this.setWeather = this.setWeather.bind(this);
    }

    getWeather (latitude, longitude) {
        var self = this;

        fetch('http://api.openweathermap.org/data/2.5/weather?lat=' + latitude + '&lon=' + longitude + '&units=metric&APPID=ed066f80b6580c11d8d0b2fb71691a2c')  
            .then (function (response) {  
                if (response.status !== 200) {  
                    console.log('Looks like there was a problem. Status Code: ' + response.status);  
                    return;  
                }

                response.json().then(function(data) {  
                    self.setWeather(data);
                });
            })

            .catch (function (err) {  
                console.log('Fetch Error :-S', err);  
            });
    }

    setWeather (forecast) {
        var main = forecast.main;
        var weather = forecast.weather[0];

        this.setState({
            main: main,
            weather: weather,
            forecast: forecast
        });
    }

    startApp () {
        this.getWeather(this.props.latitude, this.props.longitude);
    }

    componentWillMount () {
        this.startApp();
    }

    componentDidMount () {
        // window.setInterval(function () {
    //          this.getWeather();
    //  }.bind(this), 1000);
    }

  render() {
    return (
        <div className="">
            <div className="weather-data">
                <span className="temp">{Math.round(this.state.main.temp)}&#176;</span>
                <h2 className="description">{this.state.weather.description}</h2>
            </div>
        </div>
    )
  }
}

Really not sure what the issue is as the react dev tools shows that the weather component does indeed have the location set in the props which are passed down to that component.

Edit** Solved:

So the issue was that state is set asynchronously and that my weather component was rendered before state was updated.

A simple check of the values within state during the render method solved the issue.

render() {

    if (this.state.latitude != '' && this.state.longitude != '') {
      var weatherComponent = <Weather latitude={ this.state.latitude } longitude={ this.state.longitude } />
    } else {
      var weatherComponent = null;
    }

    return (
        <div className="container">
            <div className="header-container">
                {weatherComponent}
            <DateTime />
            </div>
            <div className="welcome-container">
                <Welcome name="Name" />
            </div>
      </div>
    );
  }
bcap
  • 500
  • 5
  • 14
chinds
  • 1,761
  • 4
  • 28
  • 54
  • 1
    What if you transition the initialization logic to `componentDidMount` instead of `componentWillMount`. It's possible the props haven't gotten to the component yet since the element hasn't been mounted in the DOM yet. Once mounted, then do the work. https://facebook.github.io/react/docs/component-specs.html – lux Sep 13 '16 at 19:29
  • Thanks for the suggestion unfortunately that didn't make a difference. However I have just realised that if I just return the props in the render function it works and i see the props render4ed on the page. So still not sure why the `startApp()` function cannot access them. – chinds Sep 13 '16 at 19:35

3 Answers3

19

I believe the issue is as follows. SetState happens asynchronously. Because of this, your render function is firing before the latitude and longitude props have data. If you would have some if check before rendering your Weather component you would likely not have this issue. Here is an example of what I mean.

render() {
    let myComponent;
    if(check if props has val) {
        myComponent = <MyComponent />
    } else {
        myComponent = null
    }
    return (
        <div>
            {myComponent}
        </div>
    )
}
Chaim Friedman
  • 6,124
  • 4
  • 33
  • 61
3

You have to bind startApp() and getWeather() to the component, otherwise this would be undefined.

export default class Weather extends Component {

    constructor(props) {
        super(props);
        this.state = {
          forecast: {},
          main: {},
          weather: {},
        };
        this.setWeather = this.setWeather.bind(this);
        this.getWeather = this.getWeather.bind(this);
        this.startApp = this.startApp.bind(this);
    }
    ...
}
QoP
  • 27,388
  • 16
  • 74
  • 74
  • Ok so that makes sense. With this change, i can now access props. however the latitude and longitude props for some reason return as empty strings, even now the react dev tools shows the actual value in the props. So if I `console.log(this.props)` in `componentWillMount` on in the constructor i get: `Object {latitude: "", longitude: ""}` – chinds Sep 13 '16 at 19:46
  • 1
    You have to bind `startApp()` and getLocation()` in your `App` component as well. – QoP Sep 13 '16 at 19:47
0

You need to keep render as much as clean. Avoid doing if else in render instead do if else checks with ternary operator or && operator directly in return. Do conditional check like below and only then call MyComponent something like below

render() {
    return (
        <div>
            {this.state.latitude != '' && this.state.longitude != '' ? <Weather latitude={ this.state.latitude } longitude={ this.state.longitude } />: null}
            {this.state.latitude != '' && this.state.longitude != '' && <Weather latitude={ this.state.latitude } longitude={ this.state.longitude } />}
        </div>
    )
}
Hemadri Dasari
  • 32,666
  • 37
  • 119
  • 162