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)
}
Related
I'm developing the interface of a music web app , so i fetched data from an API and stored in state, all executed by one function , to be displayed on the interphase .The code is below :
/* function fetching the data*/
function getUsChart() {
const options = {
method: 'GET',
headers: {
'X-RapidAPI-Key': '036795ec2amsh8c2b98ef8a502acp146724jsn6f3538b26522',
'X-RapidAPI-Host': 'shazam-core.p.rapidapi.com'
}
};
fetch('https://shazam-core.p.rapidapi.com/v1/charts/genre-country?country_code=US&genre_code=HIP_HOP_RAP', options)
.then(response => response.json())
.then(response => setUsHopChart(response))
.catch(err => console.error(err));
/*I stored it in state here*/
setChartImg(usHopChart[1]?.images?.coverart)
}
/*I displayed it here*/
<img src={chartImg} className='chart-img rounded-3xl' alt='chart-image'/>
The issue:
After the function is executed , the data is fetched but not stored it in the state immediately until it's executed the second time. Hence causing this :
What can i do about this please?
i think you need to move the setChartImg inside
fetch('https://shazam-core.p.rapidapi.com/v1/charts/genre-country?country_code=US&genre_code=HIP_HOP_RAP', options)
.then(response => response.json())
.then(response => {
setUsHopChart(response)
setChartImg(response[1]?.images?.coverart)
})
.catch(err => console.error(err));
/*I stored it in state here*/
I think the problem is jsx is rendered before the fetch process is done. So, it is the best approach to create a boolean loading state and initialize it with true, when it's value is true create a spinner or smth and make it false when promise returns the value.
For quick solution maybe you can do something like that:
{chartImg && <img src={chartImg} className='chart-img rounded-3xl' alt='chart-image'/>}
So what it does is when chartImg is defined (when you give it a value after promise resolves) it will render the jsx element, which was your problem.
I think you want to fetch data faster and store it in the state. There is a way to that. I will give you an example
const commentsPromise = fetch('/get-comments');
const Comments = () => {
useEffect(() => {
const dataFetch = async () => {
// just await the variable here
const data = await (await commentsPromise).json();
setState(data);
};
dataFetch();
}, [url]);
}
In this example our fetch call basically “escapes” all React lifecycle and will be fired as soon as javascript is loaded on the page, before any of useEffect anywere are called.
Even before the very first request in the roop App component will be called. It will be fired, javascript will move on to other things to process, and the data will just sit there quietly until someone actually resolves it.
I'm trying to save State twice, so I can reset it later on, but no matter what method I try, the 'setFullTrials' won't update with the saved data. The "console.log(savedData)" shows that all the data is there, so that's definitely not the problem. Not sure where I'm going wrong.
function AllTrials({Trialsprop}) {
let [savedData, setSavedData] = useState([]);
let [fullTrials, setFullTrials] = useState([]);
useEffect(() => {
//Call the Database (GET)
fetch("/trials")
.then(res => res.json())
.then(json => {
// upon success, update trials
console.log(json);
setFullTrials(json);
setSavedData(json);
})
.catch(error => {
// upon failure, show error message
});
}, []);
const resetState = () => {
setFullTrials(savedData);
//setFullTrials((state) => ({
...state,
savedData
}), console.log(fullTrials));
// setFullTrials(savedData.map(e => e));
console.log("savedData", savedData)
}
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, it will likely run before the state has actually finished updating.
Which is why we have useEffect, a built-in React hook that activates a callback when one of it's dependencies have changed.
Example:
useEffect(() => {
console.log(fullTrials)
// Whatever else we want to do after the state has been updated.
}, [fullTrials])
This console.log will run only after the state has finished changing and a render has occurred.
Note: "fullTrials" in the example is interchangeable with whatever other state piece you're dealing with.
Check the documentation for more info.
P.S: the correct syntax for useState is with const, not let.
i.e. - const [state, setState] = useState()
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
I have simple nextjs app where i want to save and print state to fetchData
This is my code
const Room = () => {
const router = useRouter();
const [fetchData, setFetchData] = useState("");
useEffect(() => {
if (router.asPath !== router.route) {
getDataNames();
console.log(fetchData); // Empty
}
}, [router]);
const getDataNames = async () => {
try {
await fetch("http://localhost:1337/rooms?_id=" + router.query.id)
.then((response) => response.json())
.then((jsonData) => setFetchData(jsonData) & console.log(jsonData)); // Data are logged in console
} catch (e) {
console.error(e);
}
};
Problem is that fetchData is empty on console log but jsonData gaves me actual data. And i have no idea what can be problem.
Ok so 3 things here. Firstly, your useEffect doesn't re-run when your fetchData value changes. You need to add it to the dependency list:
useEffect(() => {
if (router.asPath !== router.route) {
getDataNames();
console.log(fetchData); // Empty
}
}, [router, fetchData]);
this way the effect will run when fetchData changes. And of course it will only console.log it if that condition inside the effect's if condition is satisfied.
Secondly, you're mixing async/await and .then/.catch syntax, don't do that. Use one or the other. In this case you can just remove the await keyword and try/catch block, and use your existing .then code, and add a .catch to it to catch any errors.
Finally, & in Javascript is the bitwise AND operator, so not sure why you were using it there. If you think it means "and also do this", then that's incorrect. Just put the .then function inside curly braces and call the setFetchData and console.log statements one after the other:
.then((jsonData) => {
setFetchData(jsonData);
console.log(jsonData)
});
useEffect(() => {
console.log(fetchData);
}, [fetchData]);
use this hook to log your data
I'm learning how to use fetch and was trying the following syntax:
const [stuff, setStuff] = useState([]);
const request = "link-to-API";
const data = await fetch(request)
.then(response => response.json())
.catch(err => {
console.log(err);
return {} //(or [], or an empty return, or any return at all)
})
setStuff(data.hits)
Then, in the return, I have:
{stuff.map((element) => (
<Thing
title={element.label}
link={element.url}
/>
))}
Thinking I could just render an empty object whenever my fetch fails. Except, this works only when the fetch itself works. React gives me the error
"Objects are not valid as a React child (found: TypeError: Failed to
fetch)."
But I can't find any solution online. How could I handle the errors just by not rendering anything?
(that's not the only part I'm rendering, I just want to render an empty div, not conditionally render that part)
when you use await you can't use then and catch methods
It's important that you use await in async function
let data = null
try{
const response = await fetch(request)
data = response.json();
} catch(err) {
console.log(err);
}
you can try removing the await keyword, as you are using .then
also the datafetching part should be included inside useEffect
const [stuff, setStuff] = useState([]);
const request = "link-to-API";
useEffect( ()=> {
fetch(request)
.then(response => response.json())
.then(data => setStuff(data.hits))
.catch(err => {console.log(err)})
},[])