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:
- You might have mismatching versions of React and the renderer (such as React DOM)
- You might be breaking the Rules of Hooks
- 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.
CodePudding user response:
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
CodePudding user response:
In this FetchCityData(), what you will return?