简体   繁体   中英

Call function inside of a react functional component

I had my component like that before modifying and it worked.

ForecastButtons.js

export const ForecastButtons = ({ city }) => {
  const [payload, setPayload] = useState(null)
  const [error, setError] = useState(null)
  const [loading, setLoading] = useState(true)
  
  const fetchCityData = () => {
    const options = {
      method: `POST`,
    };
    fetch(`/api/weather?city=${city}`, options)
    .then((response) => {
      if(response.ok){
        return response.json().then(setPayload)
        
      }
        throw new Error('Api is not available') 
      })
    .catch(error => {
      console.error('Error fetching data: ', error)
      setError(error)
    })
    .finally(setLoading(false))
    
  }

  const location = payload?.location?.name;
  const currentTemp = payload?.current?.temp_c;

  return(
    <div className="sm:col-span-2">
      <p className="block text-sm font-medium text-gray-700">Select forecast</p>
        <button onClick={fetchCityData} className="mt-1 bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded" type='button'>
          Today
        </button>
        <p key={city?.location?.id} className='my-5'>
          { location ? `Current weather in ${location} is ${currentTemp} degrees ` : 'Please search for city to see current weather'}
        </p>
    </div>
  )
}

For the purpose of running a unit test for the fetchCityData function only, I understand, that I need to extract this function and then somehow use in my ForecastButtons component. So I tried:

ForecastButtons.js

export const FetchCityData = () => {
  const [payload, setPayload] = useState(null)
  const [error, setError] = useState(null)
  const [loading, setLoading] = useState(true)
  const options = {
    method: `POST`,
  };
  fetch(`/api/weather?city=${city}`, options)
  .then((response) => {
    if(response.ok){
      return response.json().then(setPayload)
      
    }
      throw new Error('Api is not available') 
    })
  .catch(error => {
    console.error('Error fetching data: ', error)
    setError(error)
  })
  .finally(setLoading(false))
}


export const ForecastButtons = ({ city, payload, setPayload }) => {

  const location = payload?.location?.name;
  const currentTemp = payload?.current?.temp_c;

  return(
    <div className="sm:col-span-2">
      <p className="block text-sm font-medium text-gray-700">Select forecast</p>
        <button onClick={FetchCityData} className="mt-1 bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded" type='button'>
          Today
        </button>
        <p key={city?.location?.id} className='my-5'>
          { location ? `Current weather in ${location} is ${currentTemp} degrees ` : 'Please search for city to see current weather'}
        </p>
    </div>
  )
}

This throws 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 am just learning react, so I am stuck with this issue.

I outsourced fetchCityData to a different file, where I am just doing the API call:

const fetch = require('cross-fetch');
const dev = process.env.NODE_ENV !== 'production';
const server = dev ? 'http://localhost:3000' : 'https://your_deployment.server.com';

const fetchCityData = (city) => {  
  const options = {
    method: `POST`,
  };
  return fetch(`${server}/api/weather?city=${city}`, options)
  .then((response) => {
    if(response.ok){
      return response.json()
    }
      throw new Error('Api is not available') 
    })
  .catch(error => {
    console.error('Error fetching data in city data: ', error)
  })
}

And used fetchCityData in my component like that:

import React, { useState, useEffect } from 'react';
import { fetchCityData } from '../lib/cityData'

export const ForecastButtons = ({ city }) => {
  const [payload, setPayload] = useState(null)
  const [error, setError] = useState(null)
  const [loading, setLoading] = useState(true)

  const getData = () => {
    fetchCityData(city).then((payload) => setPayload(payload));
    
    console.log(payload)
  }
  const location = payload?.location?.name;
  const currentTemp = payload?.current?.temp_c;

  return(
    <div className="sm:col-span-2">
      <p className="block text-sm font-medium text-gray-700">Select forecast</p>
        <button onClick={getData} className="mt-1 bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded" type='button'>
          Today
        </button>
        <p key={city?.location?.id} className='my-5'>
          { location ? `Current weather in ${location} is ${currentTemp} degrees ` : 'Please search for city to see current weather'}
        </p>
    </div>
  )
}

You cannot use hooks outside the body of your functional component or a custom hook

There are multiple ways to fix that, like using a custom hook or passing the callbacks to change your state to your function

export const FetchCityData = (setPayload, setError, setLoading) => {
    const options = {
      method: `POST`,
    };
    fetch(`/api/weather?city=${city}`, options)
    .then((response) => {
      if(response.ok){
        return response.json().then(data => setPayload(data))
      }
        throw new Error('Api is not available') 
      })
    .catch(error => {
      console.error('Error fetching data: ', error)
      setError(error)
    })
    .finally(() => setLoading(false))
  }
  
  
  export const ForecastButtons = ({ city, payload, setPayload }) => {
    const [payload, setPayload] = useState(null)
    const [error, setError] = useState(null)
    const [loading, setLoading] = useState(true)
  
    const location = payload?.location?.name;
    const currentTemp = payload?.current?.temp_c;
  
    return(
      <div className="sm:col-span-2">
        <p className="block text-sm font-medium text-gray-700">Select forecast</p>
          <button onClick={() => FetchCityData(setPayload, setError, setLoading)} className="mt-1 bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded" type='button'>
            Today
          </button>
          <p key={city?.location?.id} className='my-5'>
            { location ? `Current weather in ${location} is ${currentTemp} degrees ` : 'Please search for city to see current weather'}
          </p>
      </div>
    )
  }

You could also declare the function inside your component

  export const ForecastButtons = ({ city, payload, setPayload }) => {
    const [payload, setPayload] = useState(null)
    const [error, setError] = useState(null)
    const [loading, setLoading] = useState(true)
  
    const location = payload?.location?.name;
    const currentTemp = payload?.current?.temp_c;

    const FetchCityData = () => {
        const options = {
          method: `POST`,
        };
        fetch(`/api/weather?city=${city}`, options)
        .then((response) => {
          if(response.ok){
            return response.json().then(data => setPayload(data))
          }
            throw new Error('Api is not available') 
          })
        .catch(error => {
          console.error('Error fetching data: ', error)
          setError(error)
        })
        .finally(() => setLoading(false))
    }
  
return(
  <div className="sm:col-span-2">
    <p className="block text-sm font-medium text-gray-700">Select forecast</p>
      <button onClick={FetchCityData} className="mt-1 bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded" type='button'>
        Today
      </button>
      <p key={city?.location?.id} className='my-5'>
        { location ? `Current weather in ${location} is ${currentTemp} degrees ` : 'Please search for city to see current weather'}
      </p>
  </div>
)
  }

No matter how you do just don't use useState or any other hook outside a functional component

Take a look at https://reactjs.org/docs/hooks-rules.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM