skip to Main Content

I am working on a simple weather app that use OpenWeatherAPI to get the weather forcast for the day and display it. I have gotten the app to a fnuctioning point and now I want to work on optimizing the code. In order to get this application to work I am using the code below:

function App() {
  const [zipCodeData, setZipCodeData] = useState('');
  const [isValidZip, setIsValidZip] = useState(false);

  const handleZipCodeData = zipCodeData => {
    setZipCodeData(zipCodeData);
    setIsValidZip(true);
  };

  const geoLocationData = ConvertZipCode(zipCodeData, api);
  console.log(geoLocationData);


  console.log(`${zipCodeData} from parent component`);
  return (
    <ChakraProvider>
      <Grid templateColumns="repeat(5, 1fr)" templateRows="repeat(4, 1fr)">
        {!isValidZip ? (
          <ZipCodeForm sendZipCodeData={handleZipCodeData} />
        ) : (
          <WeatherBox geoLocationData={geoLocationData} />
        )}
      </Grid>
    </ChakraProvider>
  );
}

export default App;

I use the custom hook ConvertZipcode to make an API call that allows me to get Latitude and Longitude values for a zipcode that I then use to get the weather data. Here is a look at my custom hook:

const ConvertZipCode = (zipCodeData, api) => {
  const [geoInformation, setGeoInformation] = useState({
    longitude: '',
    latitude: '',
    zip: '',
    cityName: '',
  });

  useEffect(() => {
    const converZip = async () => {
      const result = await fetch(
        `http://api.openweathermap.org/geo/1.0/zip?zip=${zipCodeData}&appid=${api}`
      );

      if (!result.ok) {
        throw new Error('Could not perfom the zip to geo location converter. ');
      }

      const data = await result.json();

      const { cityName, latitude, longitude } = {
        cityName: data.name,
        latitude: data.lat,
        longitude: data.lon,
      };

      setGeoInformation({
        cityName,
        latitude,
        longitude,
      });
    };
    converZip();
  }, [zipCodeData, api]);

  return geoInformation;
};

export default ConvertZipCode;

When this code runs, it makes the api call before the zip code data is inputed by the user. I wanted to use the useEffect hook in order to stop this initial api call. I ran the code below:

function App() {
  const [zipCodeData, setZipCodeData] = useState('');
  const [isValidZip, setIsValidZip] = useState(false);
  const [geoLocationData, setGeoLocationData] = useState({});

  const handleZipCodeData = zipCodeData => {
    setZipCodeData(zipCodeData);
    setIsValidZip(true);
  };


  useEffect(() => {
    if (isValidZip && zipCodeData) {
      // Make the API call only if the zip code is valid and available
      ConvertZipCode(zipCodeData, api).then(data => {
        setGeoLocationData(data);
      });
    }
  }, [isValidZip, zipCodeData]);

  return (
    <ChakraProvider>
      <Grid templateColumns="repeat(5, 1fr)" templateRows="repeat(4, 1fr)">
        {!isValidZip ? (
          <ZipCodeForm sendZipCodeData={handleZipCodeData} />
        ) : (
          <WeatherBox geoLocationData={geoLocationData} />
        )}
      </Grid>
    </ChakraProvider>
  );
}

export default App;

When I run this code, I get the following error:
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app

I have double check and it seems like I am breaking the rules of Hooks. Can anyone point me in the right direction? Am I misusing useEffect?

Any insight would be amazing. Thank you in advanced.

I want to fix the unnecessary API calls at the start of the applicaitons rendering.

2

Answers


  1. check this link, this will solve your issue https://react.dev/learn/reusing-logic-with-custom-hooks

    Login or Signup to reply.
  2. The actual role of ConvertZipCode is nothing more than making an API call, and it has no reason to promote it as hook.
    The state is already present in the App section, and it has no reason to be duplicated. It yields no benefits, but lot of troubles.

    So keep it as a plain async function:

    const convertZipCode = async (zipCodeData, api) => {
      const result = await fetch(
        `http://api.openweathermap.org/geo/1.0/zip?zip=${zipCodeData}&appid=${api}`
      );
    
      if (!result.ok) {
        throw new Error('Could not perfom the zip to geo location converter. ');
      }
    
      const data = await result.json();
    
      const { cityName, latitude, longitude } = {
        cityName: data.name,
        latitude: data.lat,
        longitude: data.lon,
      };
    
      return{
        cityName,
        latitude,
        longitude,
      };
    };
    

    Please, note that I renamed the function to the standard camel-casing for JavaScript. A formal point, but rather important for React.
    Then, modify the App accordingly:

    function App() {
      // ...
      const [geoLocationData, setGeoLocationData] = useState({
        longitude: '',
        latitude: '',
        zip: '',
        cityName: '',
      });
    
      // ...
    
      useEffect(() => {
        if (isValidZip && zipCodeData) {
          // Make the API call only if the zip code is valid and available
          convertZipCode(zipCodeData, api).then(data => {
            setGeoLocationData(data);
          });
        }
      }, [isValidZip, zipCodeData]);
    
      // ...
    }
    

    As final point, your geoLocationData structure defines also the zip field, but that’s not fed by the function.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search