Hey I'm trying to fetch an API, but it dosnt returns anything.
I've checked and I cannot access my pre-built values inside my fetch.
How can I access my values inside the fetch ?
import React, { useState, useEffect } from 'react';
function App() {
const [ positionLat, setPositionLat ] = useState('') ;
const [ positionLong, setPositionLong] = useState('') ;
navigator.geolocation.getCurrentPosition(function(position) {
setPositionLat(position.coords.latitude);
setPositionLong(position.coords.longitude);
});
console.log(positionLat) // returns good result
console.log(positionLong) // returns good result
// I obviously need to call those values inside my fetch
useEffect(() => {
console.log(positionLat) // returns undefined
console.log(positionLong) // returns undefined
fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${positionLat}&lon=${positionLong}&appid={api_key}b&units=metric`)
.then(res => {
return res.json();
})
.then(data => {
console.log(data)
})
}, []);
return (
<div className="App">
<p>lattitude :{positionLat}</p>
<p>longitude :{positionLong}</p>
</div>
);
}
export default App;
One option is to change your effect hook to only run the main body once the values are defined:
useEffect(() => {
if (positionLat === '' || positionLong === '') {
return;
}
// rest of function
fetch(...
}, [positionLat, positionLong]);
You also need to fix your geolocation call to occur only once, on mount.
useEffect(() => {
navigator.geolocation.getCurrentPosition(function(position) {
setPositionLat(position.coords.latitude);
setPositionLong(position.coords.longitude);
});
}, []);
Another option is to split it up into two components, and only render the child component (which does the fetching) once the geolocation call is finished, which might look cleaner.
const App = () => {
const [coords, setCoords] = useState();
useEffect(() => {
navigator.geolocation.getCurrentPosition(function (position) {
setCoords(position.coords);
});
}, []);
return coords && <Child {...coords} />;
};
const Child = ({ latitude, longitude }) => {
useEffect(() => {
fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid={api_key}b&units=metric`)
.then(res => res.json())
.then(data => {
// do stuff with data
})
// .catch(handleErrors); // don't forget to catch errors
}, []);
return (
<div className="App">
<p>latitude :{latitude}</p>
<p>longitude :{longitude}</p>
</div>
);
};
Related
I have a filter function which is filtering data with state data, dataCopy and searchValue. Issue is if i don't include the data state than react gives warning and if i do include it it cause infinite loop cause the data array is changing within the useEffect. How can i make so that i don't get that warning.
Filter function
import React, { useEffect, useState } from 'react'
import Header from '../Components/Header/Header'
import Home from '../Components/Home/Home'
import "./Layout.css"
import Spinner from '../Components/Spinner/Spinner'
function Layout() {
// state for data, copy of data and spinner
const [data, setData] = useState([])
const [dataCopy, setDataCopy] = useState([])
// state for search input in Header.js (define in parent )
const [searchValue, setSearchValue] = useState("")
// changing search value
const changeSearchValue = (value) => {
setSearchValue(value)
}
// useEffect for search functionality
useEffect(() => {
const handleSearch = () => {
if (searchValue !== "") {
const searchFilter = data.filter(item =>
!isNaN(searchValue) ? item.expected_annually_bill_amount.toString().includes(searchValue) :
item.dmo_content.Ausgrid.toString().toLowerCase().includes(searchValue.toLowerCase()))
setData(searchFilter)
} else {
setData(dataCopy)
}
}
handleSearch()
}, [searchValue, dataCopy])
// useEffect for getting data from api
useEffect(() => {
// making post request to get the token
axios.post(`${process.env.REACT_APP_BASE_URL}`, { data: "" },
{
headers:
{
'Api-key': `${process.env.REACT_APP_API_KEY}`,
},
})
// after getting to token returning it for callback
.then((response) => {
return response.data.data.token
})
// using the token to call another api for the needed data
.then((tokenIs) => {
axios.post(`${process.env.REACT_APP_DATA_URL}`,
{ "session_id": `${process.env.REACT_APP_SESSION_ID}` },
{
headers:
{
'Api-key': `${process.env.REACT_APP_API_KEY}`,
'Auth-token': tokenIs,
},
})
.then((response) => {
setData(response.data.data.electricity)
setDataCopy(response.data.data.electricity)
setSpinner(false)
})
})
// catiching any error if happens
.catch((err) => {
setSpinner(false)
alert(err)
})
}, [])
return (<>
<div className='layout'>
<Header
changeSearchValue={changeSearchValue}
searchValue={searchValue}
/>
<Home data={data} />
</div>
)
}
export default Layout
Here you can eliminate data dependency by:
useEffect(() => {
const handleSearch = () => {
if (searchValue !== "") {
setData(data => data.filter(item =>
!isNaN(searchValue) ? item.expected_annually_bill_amount.toString().includes(searchValue) :
item.dmo_content.Ausgrid.toString().toLowerCase().includes(searchValue.toLowerCase())))
} else {
setData(dataCopy)
}
}
handleSearch()
}, [searchValue, dataCopy])
You can add the following comment above the dependency array for suppressing the warning
// eslint-disable-next-line react-hooks/exhaustive-deps
Link to CodeSandBox of what I am experiencing:
https://codesandbox.io/s/intelligent-chaum-eu1le6?file=/src/About.js
I am stuggling to figure out why a component will not re-render after a state changes. In this example, it is an array prop given from App.js to About.js.
a fetch request happens three times in a useEffect. Each time, it pushes it to stateArr before finally setState(stateArr)
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
setState(stateArr);
The About component is imported, and the useState variable is passed to it as a prop.
return (
<div>
<About arrayProp={state} />
</div>
);
Finally, About.js destructs the prop, and arrayProp.map() is called to render each array item on the page.
const About = ({ arrayProp }) => {
const [rerender, setRerender] = useState(0);
return (
<>
{arrayProp.map((e) => (
<div key={e.length}>
<h6>Break</h6>
{e.fact}
</div>
))}
</>
);
};
In the CodeSandBox example, I've added a button that would manually re-render the page by incrementing a number on the page.
The prop should prompt a component re-render after the fetch requests are completed, and the state is changed.
The issue is that useEffect is not behaving as described.
Each time, it pushes it to stateArr before finally setState(stateArr)
The individual fetches are not pushing to "before finally" calling setState.
const [state, setState] = useState([]);
useEffect(() => {
let stateArr = [];
function getReq() {
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
setState(stateArr);
}
getReq();
}, []);
What is actually happening is: fetch 1 is starting, then fetch 2 is starting, then fetch 3 is starting, then setState(stateArr) is being called.
There's no guarantee that these fetch will resolve before setState is called (there's similarly no guarantee that the fetches won't complete before calling setState). Though, in normal circumstances none of the fetches will resolve before setState is called.
So the only thing that's guaranteed is that state will be updated to reference the same array as stateArr. For this reason, pushing to stateArr is the same as pushing to state which is mutating state without using setState. This can cause results to be overwritten on future setState calls and it does not cause a re-render.
Well then, why does forcing re-render in About work?
As each fetch resolves it pushes values to stateArr (which is the same array as is referenced by state) for this reason the values are in the state there's just been nothing to tell React re-render (like a setState call).
Here's a small snippet which logs the promises as they complete. It also has a button that will console log the state array. (Nothing will ever render here as nothing will cause the state to update despite the state array being modified)
// Use import in normal cases; const is how use* are accessed in Stack Snippets
const {useState, useEffect} = React;
const App = () => {
const [state, setState] = useState([]);
useEffect(() => {
let stateArr = [];
function getReq() {
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
console.log('Promise 1 resolves', stateArr);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
console.log('Promise 2 resolves', stateArr);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
console.log('Promise 3 resolves', stateArr);
});
console.log('Calling Set State')
setState(stateArr);
}
getReq();
}, []);
return (
<div>
<button onClick={() => console.log(state)}>Log State Array</button>
{state.map((e) => (
<div key={e.length}>
<h6>Break</h6>
{e.fact}
</div>
))}
</div>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App/>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
<div id="root"></div>
To resolve this, simply wait for all promises to complete with Promise.all, then call setState with all the values.
const [state, setState] = useState([]);
useEffect(() => {
Promise.all([
// Promise 1
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
}),
// Promise 2
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
}),
// Promise 3
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
})
]).then((newStateArr) => {
// Wait for all promises to resolve before calling setState
setState(newStateArr);
});
}, []);
And here's a snippet demoing the result when waiting for all promises to resolve:
// Use import in normal cases; const is how use* are accessed in Stack Snippets
const {useState, useEffect} = React;
const App = () => {
const [state, setState] = useState([]);
useEffect(() => {
Promise.all([
// Promise 1
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
}),
// Promise 2
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
}),
// Promise 3
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
})
]).then((newStateArr) => {
// Wait for all promises to resolve before calling setState
setState(newStateArr);
});
}, []);
return (
<div>
{state.map((e) => (
<div key={e.length}>
<h6>Break</h6>
{e.fact}
</div>
))}
</div>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App/>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
<div id="root"></div>
I have a component the uses useEffect to fetch data from a file.
In the component i have a condiiton that only shows the content of the component if we have data.
Now how can a test the conditional part of the content i my test case?
This is what i have right now:
Component:
function MunicipalityInfo() {
const [municipalityData, setMunicipalityData] = useState({})
const fetchData = async () => {
try{
const result = await fetch(XMLFile)
const data = await result.text();
const xml = new XMLParser().parseFromString(data);
const res = XMLMapper(xml)
setMunicipalityData(res)
}catch(e){
console.log(e)
}
}
useEffect(() => {
fetchData();
}, []);
return(
<>
{ municipalityData.units &&
municipalityData.units.map((city, index) => {
return (
<Div key={index} data-testid="municipalityInfo-component" className="mt-5 p-3">
<HeaderMain data-testid="header-main">{city.City}</HeaderMain>
<HeaderSub data-testid="header-sub" className="mt-4">{city.venamn}</HeaderSub>
<BodyText data-testid="body-text">{city.Address}, {city.City}</BodyText>
<MapLink href={"#"} data-testid="map-link"><i data-testid="map-icon" className="fas fa-map-marker-alt"></i> Show on map</MapLink>
<LinkList data-testid="link-list">
<LinkListItem data-testid="list-item-first">
<Link href={city.BookingURL} data-testid="link-book-vaccination">Some text</Link>
</LinkListItem>
</LinkList>
<Calendar data={city.unit}/>
</Div>
)
})
}
<CitiesSideBar>
<Sidebar data={municipalityData.cities}/>
</CitiesSideBar>
</>
)
}
export default MunicipalityInfo;
And this is my test:
describe("<MunicipalityInfo />", () => {
it("renders without crashing", async () => {
const {queryByTestId, findByText, findByTestId} = render(<MunicipalityInfo/>, {})
expect(queryByTestId("municipalityInfo-component")).not.toBeInTheDocument();
expect(await findByTestId("municipalityInfo-component")).toBeInTheDocument(); <--- this line fails
})
})
And the error of my testcase:
TestingLibraryElementError: Unable to find an element by: [data-testid="municipalityInfo-component"]
if your problem is trying to test if something shouldn't be in the page...
use the queryBy
if you're want it to wait for something... then you want to await findBy (or wrap in a waitFor)
here's the docs: https://testing-library.com/docs/react-testing-library/cheatsheet/
I'm assuming you're mocking the fetch request so it wouldn't be the test problem...
if you're not mocking it... then you probably should mock and return either data or no data to test if it should or not render.
one way to elegantly "avoid" mocking would be by abstracting it in a custom hook:
function useCustomHook(){
const [municipalityData, setMunicipalityData] = useState({})
useEffect(() => {
fetch(XMLData)
.then((res) => res.text())
.then(async (data) => {
let xml = new XMLParser().parseFromString(data);
let result = await XMLMapper(xml)
setMunicipalityData(await result)
})
.catch((err) => console.log(err));
}, []);
return municipalityData;
}
function MunicipalityInfo({municipalityData = useCustomHook()}) { ... }
then in the test you can simply
render(<MunicipalityInfo municipalityData={'either null or some mocked data'} />)
I'm trying to fetch some data from an api and display it in jsx.First I get the users geolocation,then I call the fetch function which uses the users geolocation data to request the data from an api , afterwards
the received data from an api is used to set the weatherData state.The final step is where conditional rendering is used to show the h1 element depending if the state is defined or not.The problem is that my weatherData is always undefined,and when I try to display it returns as undefined error.Why is my weatherData undefined?
import react from "react";
import {useState} from "react";
import {useEffect} from "react";
const MainWeather=()=>{
{/*State for storing geolocation data*/}
const [status, setStatus] = useState(null);
const [weatherData,setWeatherData]=useState('');
{/*Fetches the data from an api*/}
const fetchData=(link)=>{
fetch(link)
.then(res => res.json())
.then(
(result)=>{
{/*Sets the weather data object*/}
setWeatherData(result);
console.log(result);
setStatus('data set');
},
(error)=>{
console.log(error)
}
)
}
{/*Retrieves the location from geolocation api*/}
const getLocation = async () => {
if (!navigator.geolocation) {
setStatus('Geolocation is not supported by your browser');
} else {
navigator.geolocation.getCurrentPosition((position) => {
{/*Calls the fetch function to get the data from an api*/}
fetchData(`https://api.openweathermap.org/data/2.5/onecall?lat=${position.coords.latitude}&lon=${position.coords.longitude}&exclude={part}&appid=0ea4f961aae42bfa56f75ca058577e1e&units=metric`);
}, () => {
setStatus('Unable to retrieve your location');
});
}
}
{/*Calls getLocation function on the first render*/}
useEffect(()=>{getLocation()},[])
console.log(status);
return(
<div>
{weatherData == 'undefined' ?
<h1>undefined</h1> :
<h1>{weatherData.current.temp}</h1> }
</div>
)
}
export default MainWeather;
I checked the code, what you have implemented is correct , if you are using a mac you should allow browser to fetch location , in windows a popup will come to allow it , might be browser issue check it again
still I made few changes in below the above code , just refer to it
import { useState, useEffect } from "react";
const MainWeather = () => {
const [status, setStatus] = useState(null);
const [weatherData, setWeatherData] = useState("");
const fetchData = (link) => {
fetch(link)
.then((res) => res.json())
.then(
(result) => {
setWeatherData(result);
setStatus("data set");
},
(error) => {
console.log(error);
}
);
};
const getLocation = async () => {
if (!navigator.geolocation) {
setStatus("Geolocation is not supported by your browser");
} else {
navigator.geolocation.getCurrentPosition(
(position) => {
fetchData(
`https://api.openweathermap.org/data/2.5/onecall?lat=${position.coords.latitude}&lon=${position.coords.longitude}&exclude={part}&appid=0ea4f961aae42bfa56f75ca058577e1e&units=metric`
);
},
() => {
setStatus("Unable to retrieve your location");
}
);
}
};
useEffect(() => {
getLocation();
}, []);
return (
<div>
{!weatherData ? (
<h1>{status}</h1>
) : (
<h1>{weatherData?.current?.temp} ℃ </h1>
)}
</div>
);
};
export default MainWeather;
You can refer to this codesandbox
I am using useEffect to get data from an api.
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`/api/posts/getCats`
);
const cats = await response.json();
console.log(cats);
} catch (e) {
console.error(e);
}
};
fetchData();
}, []);
The problem is when I try to use it in the return, its value is undefined.
{cats.map((data) => {
cats has value when I console.log it.
I cannot use componentDidMount because all my code is functional components.
Edit: I updated the code as per answers below but still get
TypeError: cats.map is not a function
All answers below actually make sense but I am not sure why its not working.
export default function Posts() {
const [cats, setCats] = useState();
useEffect(() => {
fetch(`/api/posts/getCats`)
.then(res => res.json())
.then(setCats)
.catch(console.error);
}, []);
return (
<div>
{cats?.map((data) => {
<h4>{data.main}</h4>
})}
</div>
)
}
This is because React renders your screen before finishing to get response from API. When you render screen, variable cats doesn't have values. You can run useEffect after each rendering. You can rerender by changing state from useEffect (This technique is often used). Do not forget to add [] or [cats] as a dependency of useEffect (second params) otherwise you will get infinite loop.
Below code works even when cats === [] or some array.
export default () => {
const [cats, setCats] = useState([])
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`/api/posts/getCats`
);
const result = await response.json();
setCats(result)
} catch (e) {
}
};
fetchData();
}, []);
return (
<div>
{cats.map(cat => <div>cat</div>)}
</div>)
}
You have to map the cats data into state.
const [cats, setCats] = useState([]);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`/api/posts/getCats`
);
const data = await response.json();
setCats(data);
} catch (e) {
console.error(e);
}
};
fetchData();
}, []);
You need to
call setCats when the response comes back (right now, you're just logging it)
.map only once cats has been populated:
const [cats, setCats] = useState();
useEffect(() => {
fetch(`/api/posts/getCats`)
.then(res => res.json())
.then(result => setCats(result.cats))
.catch(console.error);
}, []);
return (
<div>
{cats?.map((data) => {
// ...