<h1>Sunrise/Sunset Times</h1>
<div class="app">
  
</div>
body {
  padding: 0 10px;
}

input {
  width: 8em;
}
function isNumeric(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

function debounced(delay, fn) {
  let timerId;
  return function(...args) {
    if (timerId) {
      clearTimeout(timerId);
    }
    timerId = setTimeout(() => {
      fn(...args);
      timerId = null;
    }, delay);
  };
}

class CoordinatesForm extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <form>
        <LatField
          updateLat={newLat => {
            this.props.updateCoords({ lat: newLat });
          }}
          lat={this.props.lat}
        />
        <LongField
          updateLong={newLong => {
            this.props.updateCoords({ long: newLong });
          }}
          long={this.props.long}
        />
      </form>
    );
  }
}

class LatField extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      lat: this.props.lat
    };
  }

  componentDidUpdate(prevProps) {
    const lat = this.props.lat;
    if (lat !== prevProps.lat) {
      this.setState({ lat });
    }
  }

  updateLat = event => {
    let newLat = event.target.value;
    if (!newLat) {
      this.setState({ lat: 0 });
      return this.props.updateLat(0);
    }

    if (isNumeric(newLat)) {
      newLat = parseFloat(newLat);
    } else if (newLat !== "-") {
      return;
    }
    this.setState({ lat: newLat });
    this.props.updateLat(newLat);
  };

  render() {
    return (
      <label for="">
        lat:
        <input
          type="text"
          id="lat"
          value={this.state.lat}
          onChange={this.updateLat}
        />
      </label>
    );
  }
}

class LongField extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      long: this.props.long
    };
  }

  componentDidUpdate(prevProps) {
    const long = this.props.long;
    if (long !== prevProps.long) {
      this.setState({ long });
    }
  }

  updateLong = event => {
    let newLong = event.target.value;
    if (!newLong) {
      this.setState({ long: 0 });
      return this.props.updateLong(0);
    }

    if (isNumeric(newLong)) {
      newLong = parseFloat(newLong);
    } else if (newLong !== "-") {
      return;
    }
    this.setState({ long: newLong });
    this.props.updateLong(newLong);
  };

  render() {
    return (
      <label for="">
        long:
        <input
          type="text"
          id="long"
          value={this.state.long}
          onChange={this.updateLong}
        />
      </label>
    );
  }
}

class TimesDisplay extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      sunrise: "Unknown",
      sunset: "Unknown"
    };
  }

  componentDidUpdate(prevProps) {
    const lat = this.props.lat;
    const long = this.props.long;
    if (lat !== prevProps.lat || long !== prevProps.long) {
      this.updateTimesDebounced(lat, long);
    }
  }

  updateTimes = (lat, long) => {
    if (isNumeric(lat) && isNumeric(long)) {
      return fetch(`https://api.sunrise-sunset.org/json?lat=${lat}&lng=${long}`)
        .then(response => {
          if (!response.ok) {
            this.setState({
              sunrise: "Invalid",
              sunset: "Invalid"
            });
            throw Error(`${response.status} response on times request`);
          }
          return response.json();
        })
        .then(data => {
          this.setState({
            sunrise: data.results.sunrise,
            sunset: data.results.sunset
          });
        })
      .catch(error => {
        console.error(error.message);
      });
    }
  }
  
  updateTimesDebounced = debounced(500, this.updateTimes)

  render() {
    return (
      <div>
        <SunriseTime time={this.state.sunrise} />
        <SunsetTime time={this.state.sunset} />
      </div>
    );
  }
}

class SunriseTime extends React.Component {
  render() {
    return <div class="sunrise">Sunrise: {this.props.time}</div>;
  }
}

class SunsetTime extends React.Component {
  render() {
    return <div class="sunset">Sunset: {this.props.time}</div>;
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      lat: 0,
      long: 0
    };
  }

  updateCoords = updateObject => {
    this.setState(updateObject);
  };

  componentDidMount() {
    navigator.geolocation.getCurrentPosition(position => {
      const lat = position.coords.latitude;
      const long = position.coords.longitude;
      this.setState({ lat, long });
    }, error => {
      console.error('Couldn\'t get your current position from the browser');
    });
  }

  render() {
    return (
      <div>
        <CoordinatesForm
          lat={this.state.lat}
          long={this.state.long}
          updateCoords={this.updateCoords}
        />
        <TimesDisplay lat={this.state.lat} long={this.state.long} />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.querySelector(".app"));
View Compiled
Run Pen

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/basscss/8.0.10/css/basscss.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js