Sibbs
Sibbs

Reputation: 31

Class component to function component

I am trying to make this class component into a functional one that just sends back the longitude and latitude. the class component does give me long/lat but I want to use it as a function. The function should just return the longitude and latitude.

I have tried to make it a function with those functions inside of it but when the call back function getCoords is called in the navigator.getcurrent.... it gives an error saying no assignment.

import React, { Component } from 'react';


class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      lat: '',
      long: ''
    };
  }
  getLocation = () => {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(this.getCoords)
    } else {
      alert('GeoLocation not enabled');
    }
  }

  getCoords = pos => {
    console.log(pos)
    this.setState({
      lat: pos.coords.latitude,
      long: pos.coords.longitude
    })
  }

  render() {
    return (
      <div>
        <button onClick={this.getLocation}>Click me</button>
        <p>lat: {this.state.lat}</p>
        <p>long {this.state.long}</p>
      </div>
    );
  }
}

export default App;

Do I have to learn react hooks in order to solve my functional component problem?

Upvotes: 2

Views: 667

Answers (3)

Ross Sheppard
Ross Sheppard

Reputation: 870

This implements the DRY paradigm a bit more, and I feel like this is a fairly optimal solution.

import React, { useEffect, useState } from 'react';

export function App() {
  const [loc, setLoc] = useState({});
  const [isLoaded, setIsLoaded = useState(false);

  useEffect(() => {
     const fetchMap = async () => {
       const response = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?latlng=${loc.lat},${loc.long}&key=${INSERT_API_KEY_HERE}`);

       // Do something with the response

       setIsLoaded(true);
     };

    fetchMap();
  });


  const getLocation = () => {
    const { geolocation } = navigator;

    if (geolocation) {
      geolocation.getCurrentPosition(getCoordinates);
    } else {
      alert('GeoLocation not enabled');
    }
  };

  const getCoordinates = (pos) => {
    const { latitude, longitude } = pos.coords;
    setLoc({ lat: latitude, long: longitude });
  }; 

  // You can this conditionalize your return statement based
  // on the isLoaded state variable. "if (isLoaded)"

  return (
    <div>
      <button onClick={getLocation}>Click me</button>
      <p>lat: {loc.lat}</p>
      <p>long {loc.long}</p>
    </div>
  );
};

export default App;

Upvotes: 0

Nathan Jensen
Nathan Jensen

Reputation: 141

The issue is that grabbing coordinates via navigator is an asynchronous request, so the fetch is going out before the coordinates are available. Using the useEffect hook, you could tell it to fetch the data once the lat and long are updated like so:

const App = () => {
    const pos = usePos()


    useEffect(() => {
        getDataWithCoords()
    }, [pos.lat, pos.long])

    const getDataWithCoords = () => {
        fetch(`https://maps.googleapis.com/maps/api/geocode/json?latlng=${pos.lat},${pos.long}&key=YOUR_API_KEY`)
            .then(res => res.json())
            .then(res => console.log(res))
            .catch(err => console.log(err))
    }

    return (
        <div>
            <p>Lat {pos.lat}</p>
            <p>Long: {pos.long}</p>
        </div>
    )
}

May not be a perfect example, but it works!

Also, make sure you are not posting API keys publicly, you will want to keep those private to your local dev environment. Good luck!

Upvotes: 0

Nathan Jensen
Nathan Jensen

Reputation: 141

Hooks would definitely be the way to go so that you can still maintain the position data in state. Here is the same code but with the useState hook:

const App = () => {
    const [pos, setPos] = useState({lat: "", long: ""})
    const  getLocation = () => {
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(getCoords)
        } else {
          alert('GeoLocation not enabled');
        }
      }

    const getCoords = (pos) => {
        console.log(pos)
        setPos({
          lat: pos.coords.latitude,
          long: pos.coords.longitude
        })
      }

    return (
        <div>
        <button onClick={getLocation}>Click me</button>
        <p>lat: {pos.lat}</p>
        <p>long {pos.long}</p>
        </div>
    );

}

export default App;

You can see it is not a huge difference, mostly just removing the this and this.state keywords and replacing them with pos.lat and setPos.

From here if you wanted your component to just return the coords instead of JSX, you would create your own custom hook that would look like this:

const usePos = () => {
    const [pos, setPos] = useState({lat: "", long: ""})
    const  getLocation = () => {
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(getCoords)
        } else {
          alert('GeoLocation not enabled');
        }
      }

    const getCoords = (pos) => {
        console.log(pos)
        setPos({
          lat: pos.coords.latitude,
          long: pos.coords.longitude
        })
      }

    getLocation()

    return { lat: pos.lat, long: pos.long }

}


const App = () => {
    const pos = usePos()
    return (
        <div>
            <p>Lat {pos.lat}</p>
            <p>Long: {pos.long}</p>
        </div>
    )
}

Upvotes: 1

Related Questions