useState and useEffect not playing nice with an API call [duplicate] - javascript

This question already has answers here:
When should I use a return statement in ES6 arrow functions
(6 answers)
Closed last month.
const [inputText, setInputText] = useState('');
const [searchTerm, setSearchTerm] = useState('');
const [data, setData] = useState(null);
const handleChange = (event) => {
setInputText(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
setSearchTerm(inputText)
console.log(inputText)
setInputText('')
};
useEffect(() => {
if (searchTerm) {
console.log('starting fetch with -> ', `https://imdb-api.com/en/API/SearchTitle/k_ix11kdvq/${searchTerm}`)
fetch(`https://imdb-api.com/en/API/SearchTitle/k_ix11kdvq/${searchTerm}`)
.then(res => {
res.json()
console.log('json data')
})
.then(json => {
setData(json)
console.log('setting data to this value', json)
})
.then(console.log('current data value after setData runs -> ', data))
}
}, [searchTerm])
Wanted to redo a simple HTML webpage I build in my bootcamp within React.
As you can see I have some super professional console logs in here to track just wth is going on with this data.
inception
App.js:36 starting fetch with -> https://imdb-api.com/en/API/SearchTitle/k_ix11kdvq/inception
App.js:45 current data value after setData runs -> null
App.js:40 json data
App.js:44 setting data to this value undefined
index.js:6 data value within body component currently is undefined
installHook.js:1861 data value within body component currently is undefined
index.js:6 data value within body component currently is undefined
installHook.js:1861 data value within body component currently is undefined
App.js:36 starting fetch with -> https://imdb-api.com/en/API/SearchTitle/k_ix11kdvq/inception
App.js:45 current data value after setData runs -> undefined
App.js:40 json data
App.js:44 setting data to this value undefined
Yeeeeeaaah....
It's not working asynchronously at all. Obviously I'm being stupid, but I just can't figure out where.
I'm trying to utilize useEffect to make an API call, wait for the data, then jsonify it, and setData to that value so that I can display it.
I tried using axios and doing an async/await function, no dice.
Please help 😊

You're not returning your res.json(). Since you're not returning anything, the next .then is empty.
useEffect(() => {
if (searchTerm) {
console.log('starting fetch with -> ', `https://imdb-api.com/en/API/SearchTitle/k_ix11kdvq/${searchTerm}`)
fetch(`https://imdb-api.com/en/API/SearchTitle/k_ix11kdvq/${searchTerm}`)
.then(res => {
console.log('json data')
return res.json()
})
.then(json => {
setData(json)
console.log('setting data to this value', json)
})
.then(console.log('current data value after setData runs -> ', data))
}
}, [searchTerm])

Related

Setting a state but still getting error when trying to use after it

I'm trying to get pinned article
const getCurrentlyPinned = async() =>{
setLoader(true)
await firestore()
.collection('admin_control')
.doc('currently_Pinned')
.get()
.then(snapshot =>{
const data = snapshot.data();
setpinnedNewsID(data.pinnedNewsId)
})
}
useEffect(() => {
getCurrentlyPinned().then(()=>{
console.log(pinnedNewsID)
})
}, [])
therefore calling it from useEffect and console logging it in .then function, but I'm getting its value as undefined. I dont know why I'm getting this.
That's a very common stuff, I guess there must be some similar question over stackoverflow but let me answer it for you. States are asynchronous so it takes a bit time of course to set it.
Just do the following stuff.
useEffect(() => {
getCurrentlyPinned()
}, [])
useEffect(() => {
console.log(pinnedNewsID)
}, [pinnedNewsID])
the state which you're setting in firebase function, just pass it in the dependency array of another useEffect so that you will console.log only when its value has been changed(or set)
async functions take some time to return data.
useEffect trying to access the data before it's ready. bc it runs immediately after the component renders. that's why pinnedNewsID value is Undefined.
do this instead: use 2 useEffect hooks.
useEffect(() => { getCurrentlyPinned() }, [])
useEffect(() => { console.log(pinnedNewsID) }, [pinnedNewsID])

How to access data outside of a Promise

I'm making a react app that sends an API call to OpenWeather to get the weather data for a city (specified by the user). Here's what the request for that call looks like:
async function getAPI() {
const apiCall = await axios.get(apiLink).then(res => {
res = {
temp : res.data.main.temp - 273.15,
weatherIcon : res.data.weather[0].icon,
windSpeed : res.data.wind.speed
}
return res
});
return apiCall
}
const weatherData = getAPI()
Notice that I try to store the data I want from the API response in a variable called weatherData. That way I can simply call that variable whenever I need, heres an example of HTML code that uses this variable:
<p>
temperature is {weatherData.temp} Celcius
</p>
This results in weatherData.temp simply not showing up on the browser side for some reason. A console.log(weatherData) prints this in the console:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Object
temp: 29.53
weatherIcon: "04d"
windSpeed: 1.59
[[Prototype]]: Object
How do I extract the data from the promise in a way that allows me to easily refer to said data for use in HTML code?
Answer below is if you are using functional components and react hooks.
You can can go two directions:
Using a try catch block:
const fetchWeather = async () => {
try {
const res = await axios.get(apiLink);
console.log(res);
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// you can then set the data you need to your state to render it.
} catch (error) {
// handle error
}
}
Or you can use .then .catch
const fetchWeather = async () => {
axios.get(apiLink)
.then((res) => {
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// set the data you need from the respones to your state.
})
.catch((err) => {
// handle error
})
}
In both cases you can just call the function in your useEffect hook.
useEffect(() => {
fetchWeather()
}, [])
In general my preference goes to set the response you get from the Api into the local state (meaning the state of your page/component). And then rendering the state to your jsx.
So if you are using react hooks, your state could look like this:
const [weather, setWeather] = useState({});
Last Edit:
Finally you can just refer to your state within your jsx/html. Assuming your weather state looks like this:
{
temp: '50 degrees'
}
In your JSX you can just refer to it this way:
<>
<div>{weather.temp}</div>
</>

useEffect not triggered on state update by its dependency

I am trying to implement a seamless login and trigger another function when login is successful
const [token, setToken] = useState();
useEffect(() => {
async function attemptLogin() {
await fetch('http://localhost:3000/login')
.then(response => response.json())
.then(data => console.log(data.data))
.then(data => setToken(JSON.stringify(data))) // {data: 'Logged in'}
.catch(err => {
console.error('error occured: ', err.message)
});
}
attemptLogin();
}, []);
useEffect(() => {
console.log('should run after token update');
console.log(token); //undefined
// another fetch goes here since we needed to login to get token for API
}, [token]);
So the useEffect with the dependency runs before the one used on mounting. why? Shouldn't it run only when state changes? Does it run on initialization or something? Then why not run when I get the data from my fetch?
The problem is with this piece of code:
.then(data => console.log(data.data))
.then(data => setToken(JSON.stringify(data)))
In .then(), data is not passed on further to the next .then(). That is why the second .then() becomes something like setToken(JSON.stringify(undefined)).
console.log(JSON.stringify(undefined))
As you see that will return undefined so you are doing setToken(undefined).
You probably want to do .then(data => data.data) instead of .then(data => console.log(data.data)), so you are actually returning something.
Note: Do not need the second .then() because there is nothing async here :
.then(data => setToken(JSON.stringify(data.data)))
Also,
useEffect(() => {
console.log('should run after token update');
console.log(token); //undefined
// another fetch goes here since we needed to login to get token for API
}, [token]);
the callback in this useEffect will run on the first render (mount) because token is given a value at that time (although undefined). This counts as a change for React, because earlier the variable did not even exist (a codesandbox demonstrating this).
From the docs:
Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update.
You can check for the first mount using a ref if you do not want this to run on first mount or check the value for undefined using if.
Try this out:
async function attemptLogin() {
let res = await fetch('http://localhost:3000/login')
let jsonData = await res.json();
setToken(jsonData)
}

useEffect fetch request is pulling data twice

What I Want:
I'm pulling data from an api, then setting the data to state. I've done this inside a useEffect hook, but when I console.log the data afterwards, it's displaying the data twice, sometimes 4 times. I'm at a loss as to why this is happening.
What I've Tried:
console.log within useEffect to see data from source
disabling react developer tools within chrome.
My Code:
// make api call, assign response to data state
const [apiData, setApiData] = useState();
useEffect(() => {
async function fetchData() {
try {
await fetch('https://restcountries.com/v3.1/all')
.then(response => response.json())
.then(data => setApiData(data));
} catch (e) {
console.error('Error fetching api data', e);
};
};
fetchData();
}, []);
console.log(apiData);
Result of console.log:
As was mentioned in the other comment this is due to effects being "double invoked" in strict-mode.
A common solution and one I believe has been suggested by the React team (although am struggling to find where I read this) is to use useRef.
// make api call, assign response to data state
const [apiData, setApiData] = useState();
const hasFetchedData = useRef(false);
useEffect(() => {
async function fetchData() {
try {
await fetch('https://restcountries.com/v3.1/all')
.then(response => response.json())
.then(data => setApiData(data));
} catch (e) {
console.error('Error fetching api data', e);
};
};
if (hasFetchedData.current === false) {
fetchData();
hasFetchedData.current = true;
}
}, []);
If you are using React version 18+, StrictMode has a behavior change. Before it wasn't running effects twice, now it does to check for bugs. ( Actually, it does mount-remount, which causes initial render effects to fire twice)
https://reactjs.org/blog/2022/03/29/react-v18.html#new-strict-mode-behaviors

Setting state on array logs empty array multiple times

I am trying to fetch data from a backend server and hold it in an array. After I have done this I want to pass the array to another
component. Although, when I try and populate the array and pass it to my component, I get multiple empty arrays passed rather than an array with data.
I first initialise the state of the array using useState()
const [data, setData] = useState([]);
I then have a function that fetches data from the backend and attempts to populate data.
useEffect(() => {
const fetchData = () => {
fetch('/data')
.then((res) => res.json())
.then((data) => {
for (const property in data) {
setDailyCases([...dailyCases].push(`${data[property]}`));
}
});
}
fetchData();
},[])
When I pass this data to another component: <DataComp data={data}, I don't get the data I was expecting.
When I console.log(props.data) this is the output:
Which is strange beacuse If I console.log() while running the data loop all the data is visible:
How can I make sure the data array is updating correctly, and when passed I get one array of all the data?
Here is the DataComp component:
const DataComp = (props) => {
console.log(props.cases)
return (
<h1>Testing</h1>
)
}
export default DataComp
Using #Fardeen Panjwani answer my component is getting the correct data, although I am now getting more outputs to the console that expected?
You're never calling setData with the fetched data as parameter.
useEffect(() => {
const fetchData = () => {
fetch('/data')
.then((res) => res.json())
.then((_data) => { // using "_data" in order to avoid clash with the state-hook
setData(_data) // <= this line is responsible for populating the "data" value.
for (const property in _data) {
setDailyCases([...dailyCases].push(`${_data[property]}`));
}
});
}
fetchData();
},[])
In order to update data's value, you need to call the setData method.

Categories