Reputation: 223
I'm making a fetch request to this API and I'm successfully getting the data and printing it to the console. However I'm new to asynchronous calls in Javascript/React. How do add async/await in this code to delay the render upon successful fetch? I'm getting the classic Uncaught (in promise) TypeError: Cannot read property '0' of undefined
because I believe that the DOM is trying to render the data that hasn't been fully fetched yet.
import React, { useEffect, useState } from "react";
export default function News() {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [stories, setStory] = useState([]);
useEffect(() => {
fetch(
"http://api.mediastack.com/v1/news"
)
.then((res) => res.json())
.then(
(result) => {
setIsLoaded(true);
setStory(result);
console.log(result.data[0]); // printing to console to test response
console.log(result.data[0].title); // printing to console to test response
},
(error) => {
setIsLoaded(true);
setError(error);
}
);
}, []);
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div>
<p>{stories.data[0].title} </p> // this is the where render error is
</div>
);
}
}
Upvotes: 0
Views: 1597
Reputation: 1075
async/await is just another form to retrieve asynchronous data, like you are doing with then.
The message:
Cannot read property '0' of undefined
means that 'result.data' is undefined.
Anyway, if it entered the "then" callback it always means that the request was fetched, there is no such thing as "half fetched" or "fully fetched".
I suggest you to use the debugger, by placing
debugger;
right in the beginning of the 'then' callback to ensure what your result is. or you may also console.log the result.
Just to clarify:
myFunctionThatReturnsPromise.then(response => //do something with response)
is the same as
await response = myFunctionThatReturnsPromise;
You might consider using stories?.data?.[0]?.title
to fix this problem.
Upvotes: 1
Reputation: 7915
The problem is that your isLoaded
state variable is updated BEFORE stories
, despite the fact you set the former state first. Here is how to fix this:
import { useEffect, useState } from "react";
export default function App() {
const [error, setError] = useState(null);
const [stories, setStory] = useState(null);
useEffect(() => {
fetch("your_url")
.then((res) => return res.json())
.then((result) => {
setStory(result);
console.log("Success ", result);
})
.catch((error) => {
console.log("Error", error);
});
}, []);
if (error) {
return <div>Error: {error.message}</div>;
} else if (!stories) {
return <div>Loading...</div>;
} else {
return (
<div>
<p>stories.data[0].title} </p>
</div>
);
}
}
Get rid of the isLoaded
var altogether and use the stories
var to indicate that it's being loaded.
If you want to add artificial load time to your api call. You can do something like this:
useEffect(() => {
fetch("your_url")
.then((res) => return res.json())
.then((result) => {
setTimeout(() => setStory(result), 2000)
})
.catch((error) => {
console.log("Error", error);
});
}, []);
This will add 2 seconds before setting your state thus letting you see what your loader looks like.
Upvotes: 1
Reputation: 195
your error will not be gone with async await because you are calling a nested empty object which has no value. render method in react is prior to the useEffect you have used. you can approach two solutins:
1.use optional chaining es20 feature like this:
<p>{stories?.data?.[0]?.title} </p>
2.use nested if statement before the p tag to check whether it has data or not:
it seems the first option is much cleaner
Upvotes: 0
Reputation: 567
You already have async code using Promise.then
so async/await
will not help you here - it's just a different way of writing Promise-based functionality.
Your problem is just as you say - React is trying to render your else
block before stories
has anything for you to render. I think you just need to write stricter conditions, e.g.
if (error) {
return <div>Error: {error.message}</div>;
}
if (!isLoaded || !stories.data.length) {
return <div>Loading...</div>;
}
return (
<div>
<p>{stories.data[0].title} </p> // this is the where render error is
</div>
);
Upvotes: -1