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)})
},[])
Related
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)
}
I am doing get request to backend to fetch data from database,
Im doing something like :
const loadData = async () => {
const response = await fetch(URL);
const data = await response.json();
setOrdersData(data.data);
};
useEffect(() => {
loadData();
console.log(OrdersData)
}, []);
when i console.log(OrdersData) it console.log 6 times thus in rendering the data it rendering it 6 times as well, i also tried to set dependency in useEffect like as follow:
const loadData = async () => {
const response = await fetch(URL);
const data = await response.json();
setOrdersData(data.data);
};
useEffect(() => {
loadData();
setLoading(false)
console.log(OrdersData)
}, [loading]);
But still when i render OrdersData it rendering it 6 times even though the response result is only one object, i couldn't figure it out how to not duplicate the data.
To prevent unnecessary renders try to use the useCallback hook in the loadData as so:
const loadData = useCallback(async () => {
try {
const response = await fetch(URL);
const data = await response.json();
setOrdersData(data.data);
} catch (err) {
//do something
}
}, [])
Remember to import as hook, and use the dependecies as you please like useEffect or useMemo.
Hoping it works remember also to unmount the the side effects in useEffect with return statement
I'm trying to fetch data that contains a JSON array that looks like this
[
{
"prof": "Jason Crank",
"views": "2",
//etc etc
}
]
and I'm trying to transform the JSON object into an JavaScript array I can map and render the appropriate amount of blocks, I'm using typescript and this is my code
const [notesArr, setNotesArr] = useState<notesInterface[]>([])
const fetchNotes = async (active) => {
try{
const response = await fetch("my server", {
method:"GET",
headers: {'Content-Type': 'application/json'}
})
.then(resp => JSON.parse(JSON.stringify(resp)))
.then(result => setNotesArr(result))
return(
<div>
{notesArr.map((notes) => <NoteThumb link={notes.link} title={notes.title} semester={notes.semester} prof={notes.prof} timestamp={notes.timestamp} likes={notes.likes} views={notes.views} pages={notes.pages} isBookmarked={notes.isBookmarked}/>)}
</div>
)
}catch(error){
console.log(error)
}
}
and the interface I'm using looks like this
interface notesInterface {
prof: string
views: number
}
When trying this I get an error that says Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
I'd appreciate any help and I can offer more snippets of my code if necessary
To summarise what you're being told in the comments above...
Your functional component needs to return the JSX node, not your fetchNotes function
Use useEffect to execute fetchNotes on component mount
const Notes: React.FC = () => {
const [notesArr, setNotesArr] = useState<notesInterface[]>([])
useEffect(async () => {
try {
const response = await fetch("my server")
if (!response.ok) {
throw new Error(`${response.status}: ${await response.text()}`)
}
setNotesArr(await response.json())
} catch (err) {
console.error(err)
}
})
return (
<div>
{notesArr.map(note => <NoteThumb {...note} />)}
</div>
)
}
I cleaned up your fetch() call as well; GET is the default method and GET requests do not have Content-type due to there being no request body.
Does anyone know why this fetch continues to fire. I have also tried putting it inside a useEffect with no luck. It should only fire once to return once imdbID has loaded.
const WatchOnList = ({ imdbId }) => {
const [locations, setLocations] = useState([])
var headers = new Headers();
headers.append("x-api-key", "API_KEY")
var requestOptions = {
method: 'GET',
headers: headers,
crossDomain: true,
redirect: 'follow'
};
async function fetchData() {
const res = await fetch(`${awsApiUrl}?imdb_id=${imdbId}`, requestOptions);
res
.json()
.then((res) => {
setLocations(res)
console.log(locations)
})
.catch(error => console.log('error', error));
}
fetchData();
With the current structure, the request will fire on every re-render. Which will be quite often in a React app. useEffect is the right place for such a function. But there are some caveats:
You can't make useEffect async, you have to create an async function inside the hook instead and call it afterward.
useEffect will per default run on every update, so you have to tell it explicitly to only run once (like componentDidMount for class components). This can be done by passing an empty array as the second parameter. The hook watches parameters specified in this array and only updates when one of them changes. As it is empty, it only fires once on initialization.
This should work:
useEffect(() => {
async function fetchData() {
const res = await fetch(`${awsApiUrl}?imdb_id=${imdbId}`, requestOptions);
res
.json()
.then(res => {
setLocations(res);
console.log(locations);
})
.catch(error => console.log("error", error));
}
fetchData();
}, []);
Read more about the behavior of hooks here and here.
I'm trying to manipulate JSON data received from an API url (this is my first time handling this type of work)
The following function returns a promise of a 20 element array:
const articles = () => {
return fetch(url)
.then(res => res.json())
.then(post => post.articles);
};
Console view:
Now, I'd like to extract the elements from the array - I tried something like:
articles()[0].name
but this doesn't work and I'm not sure of an alternative way to go about this? Appreciate your help. Thanks
Your articles fucntion returns a promise. You have to consume the promise (more on MDN):
articles().then(articleArray => {
console.log(articleArray);
});
or within an async function:
const articleArray = await articles();
console.log(articleArray);
Side note: Your fetch code is missing a check for HTTP success (HTTP failure isn't a rejection). You're by far not the only person who misses out this check, so much so that I've written a post on my anemic blog about it. With the check:
const articles = () => {
return fetch(url)
.then(res => {
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
return res.json();
})
.then(post => post.articles);
};