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:
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.