async/await is not working with react.js inside ComponentDidMount - reactjs

I'm trying to fetch data using axios.
This code is working:
componentDidMount(){
const posts = axios.get("https://jsonplaceholder.typicode.com/posts");
console.log(posts);
}
but this doesn't:
async componentDidMount(){
const data = await axios.get("https://jsonplaceholder.typicode.com/posts");
console.log(data)
}
Any idea why it's not working?

There is nothig special in componentDidMount that could prevent it from being async. This particular issue looks like an Axios issue. It's pretty easy to check using fetch:
async componentDidMount() {
const data = await fetch("https://jsonplaceholder.typicode.com/posts");
console.log(await data.json())
}

It is not quite clear what you are referring to when saying "this code works":
The output in the console will not be your lists of posts, right?
But you don't need to use async/await, they are just syntactic sugar.
The value synchronously returned by the call to axios.get is a Promise so you can also use then and catch:
componentDidMount(){
axios.get("https://jsonplaceholder.typicode.com/posts")
.then(console.log)
.catch(console.error);
}
This way you you don't need async/await support in react which seems to be non trivial to set up correctly as this article states: https://www.valentinog.com/blog/await-react/
be aware that the component will render anyway since it not aware that you are still waiting for the data to load. So when the data loads and you updated your state it will need to render again.

Your both the snippets are working fine.
Your code,
componentDidMount(){
const posts = axios.get("https://jsonplaceholder.typicode.com/posts");
console.log(posts);
}
Here console.log(posts) will return only Promise and not actual data.
And using async/await,
async componentDidMount(){
const posts = await axios.get("https://jsonplaceholder.typicode.com/posts");
console.log(posts);
console.log(posts.data);
console.log(JSON.stringify(posts.data))
console.log(JSON.stringify(posts.data[0]));
}
Here also,
console.log(posts) - Will return Promise object and not actual data
console.log(posts.data) - This will return actual data array
console.log(JSON.stringify(posts.data)) - This will return stringified version of actual data
console.log(JSON.stringify(posts.data[0])) - This will return stringified version of first record in actual data.
Demo

Related

RTK Query response state

I'm trying to convert some Axio code to RTK query and having some trouble. The 'data' response from RTK query doesn't seem to act like useState as I thought.
Original axio code:
const [ importantData, setImportantData ] = useState('');
useEffect(() => {
async function axiosCallToFetchData() {
const response = await axiosAPI.post('/endpoint', { payload });
const { importantData } = await response.data;
setImportantData(importantData);
}
axiosCallToFetchData()
.then((res) => res)
.catch((error) => console.log(error));
}, []);
const objectThatNeedsData.data = importantData;
New RTK Query code
const { data, isSuccess } = useGetImportantDataQuery({ payload });
if(isSuccess){
setImportantData(data.importantData);
}
const objectThatNeedsData.data = importantData;
This however is giving me an infinite render loop. Also if I try to treat the 'data' object as a state object and just throw it into my component as:
const objectThatNeedsData.data = data.importantData;
Then I get an undefined error because it's trying to load the importantData before it's completed. I feel like this should be a simple fix but I'm getting stuck. I've gone through the docs but most examples just use the if statement block to check the status. The API calls are being made atleast with RTK and getting proper responses. Any advice?
Your first problem is that you always call setImportantData during render, without checking if it is necessary - and that will always cause a rerender. If you want to do that you need to check if it is even necessary:
if(isSuccess && importantData != data.importantData){
setImportantData(data.importantData);
}
But as you noticed, that is actually not necessary - there is hardly ever any need to copy stuff into local state when you already have access to it in your component.
But if accessing data.importantData, you need to check if data is there in the first place - you forgot to check for isSuccess here.
if (isSuccess) {
objectThatNeedsData.data = data.importantData;
}
All that said, if objectThatNeedsData is not a new local variable that you are declaring during this render, you probably should not just modify that during the render in general.

How to use { useQuery } from react-query while iterating over an array of data

I'm trying to integrate react-query into my React project.
What I have is a custom hook - useBundles which fetches data from a GraphQl endpoint as shown below -
function useBundles() {
const { data, status, isSuccess } = useQuery(
'SaleBundles',
async () => await request(endpoint, gql`query {${saleQuery}}`),
);
return { data, status, isSuccess };
}
I use the return value in my component like this const { data, status, isSuccess } = useBundles(); which works perfectly fine.
Now, what I want to do is, for each item in data I want to call another endpoint (a REST endpoint this time) and I have a seperate async function for that called getData(data: Array) which uses async-await to fetch data from my REST endpoint.
I could just call getData in useBundles passing to it data as an argument. But since getData is async it is required to use await with it (which I can't because I can't define a hook as async).
As an alternative, I tried not to use getData in useBundles but directly call the endpoint using axios like this -
data.forEach((item) => useQuery("some-unique-key", axios.get(<endpoint>)))
This gives me an error saying that useQuery cannot be used in a loop.
I'm kinda stuck at this point as how to proceed. Would appreciate any help. Thanks.
There are basically two ways to solve this:
useQueries
useQueries can be used with a fixed array, but also with a dynamic one, so:
useQueries(data.map(item => ...))
would work. It will give you an array of QueryResults back.
map and create a component
data.map(item => <Item {...item} />
then, you can call useQuery inside the Item component.
Both approaches will yield concurrent requests, the difference is mainly where you have access to the response data: With useQueries, you have access to all of them in your root component. With the second approach, each Item will only have access to its own data. So it just depends on your needs / use-case which version you prefer.

Get All The youtube video from specific channel in reactjs

I am trying to get video from a specific channel in youtube using youtube data api using reactjs.But my api query doesnot return anything.Here is what i've tried for getting the video.What am i doing wrong here.
componentDidMount() {
console.log('componentDidMount colling ...');
fetch('https://www.googleapis.com/youtube/v3/channels?key="MYAPIKEY"&channelId="MYCHANNELID"&part=snippet,id&order=date&maxResults=2')
.then(results => {
this.setState({videos: results.json()});
this.setState({playingVideoId: this.state.videos[this.index]});
console.log("videos",this.state.videos)
console.log("videos",this.state.playingVideoId)
})
}
First try to use postman to see if URL is returning correct data.
Second: setState is asynchronous which means, if you console.log right after setting state, you won't get correct results. Also, your second call to setState is using data from first call therefore you won't get right results. Just call setState once and use callback to log what you got.
componentDidMount() {
console.log('componentDidMount colling ...');
fetch('https://www.googleapis.com/youtube/v3/channels?key="MYAPIKEY"&channelId="MYCHANNELID"&part=snippet,id&order=date&maxResults=2')
.then(results => {
const videosObj = results.json();
this.setState({
videos: videosObj,
playingVideoId: videosObj[this.index]
}, (updatedState) => {
console.log("videos", updatedState.videos);
console.log("videos", updatedState.playingVideoId);
});
})
}

Component Renders too early

I have a class that does a lot of API Calls to populate the state so I'm doing it in the componentDidMount() lifecycle hook like this:
async componentDidMount() {
await this.populateQuote();
await this.populateCustomers();
await this.populateEmployees();
await this.populatePropertyTypes();
}
and each of this functions are getting some data and setting some values in the state, now my problem is that every time one of the promises resolves it re-renders the page which I'd like to avoid, is there any way around this?
You should use Promise.all to ensure that all Promises in an array gets resolved before perform an operation
async componentDidMount(){
const calls = [call1(), call2(), call3()]
const results = await Promise.all(calls)
console.log(results[0],results[1],results[2])
}
Use Promise.all() to fetch all data and then this.setState() to perform the only re-render
async componentDidMount() {
const [quote, customers, employees, propertyTypes] = await Promise.all([
this.populateQuote(),
this.populateCustomers(),
this.populateEmployees(),
this.populatePropertyTypes()
]);
this.setState({
quote,
customers,
employees,
propertyTypes
});
}
What you could use is Promise.all, to initiate parallel requests for your async calls.
async componentDidMount() {
const [quotes, customers, employees, propertyTypes] = await Promise.all([
this.getQuotes(),
this.getCustomers(),
this.getEmployees(),
this.getPropertyTypes()
]);
}
And then you would set corresponding state based on your results. This can be achieved only if your calls are independent of other async results (if next is dependent on previous, you'll have to await each function and pass it required result from previous call).
Most important thing for your answer is that you should not call setState before fetching all required results, because each call to setState initiates new render.

Is using async componentDidMount() good?

Is using componentDidMount() as an async function good practice in React Native or should I avoid it?
I need to get some info from AsyncStorage when the component mounts, but the only way I know to make that possible is to make the componentDidMount() function async.
async componentDidMount() {
let auth = await this.getAuth();
if (auth)
this.checkAuth(auth);
}
Is there any problem with that and are there any other solutions to this problem?
Let's start by pointing out the differences and determining how it could cause troubles.
Here is the code of async and "sync" componentDidMount() life-cycle method:
// This is typescript code
componentDidMount(): void { /* do something */ }
async componentDidMount(): Promise<void> {
/* do something */
/* You can use "await" here */
}
By looking at the code, I can point out the following differences:
The async keywords: In typescript, this is merely a code marker. It does 2 things:
Force the return type to be Promise<void> instead of void. If you explicitly specify the return type to be non-promise (ex: void), typescript will spit an error at you.
Allow you to use await keywords inside the method.
The return type is changed from void to Promise<void>
It means you can now do this:
async someMethod(): Promise<void> { await componentDidMount(); }
You can now use await keyword inside the method and temporarily pause its execution. Like this:
async componentDidMount(): Promise<void> {
const users = await axios.get<string>("http://localhost:9001/users");
const questions = await axios.get<string>("http://localhost:9001/questions");
// Sleep for 10 seconds
await new Promise(resolve => { setTimeout(resolve, 10000); });
// This line of code will be executed after 10+ seconds
this.setState({users, questions});
return Promise.resolve();
}
Now, how could they cause troubles?
The async keyword is absolutely harmless.
I cannot imagine any situation in which you need to make a call to the componentDidMount() method so the return type Promise<void> is harmless too.
Calling to a method having return type of Promise<void> without await keyword will make no difference from calling one having return type of void.
Since there is no life-cycle methods after componentDidMount() delaying its execution seems pretty safe. But there is a gotcha.
Let's say, the above this.setState({users, questions}); would be executed after 10 seconds. In the middle of the delaying time, another ...
this.setState({users: newerUsers, questions: newerQuestions});
... were successfully executed and the DOM were updated. The result were visible to users. The clock continued ticking and 10 seconds elapsed. The delayed this.setState(...) would then execute and the DOM would be updated again, that time with old users and old questions. The result would also be visible to users.
=> It is pretty safe (I'm not sure about 100%) to use async with componentDidMount() method. I'm a big fan of it and so far I haven't encountered any issues which give me too much headache.
Update April 2020:
The issue seems to be fixed in latest React 16.13.1, see this sandbox example. Thanks to #abernier for pointing this out.
I have made some research, and I have found one important difference:
React does not process errors from async lifecycle methods.
So, if you write something like this:
componentDidMount()
{
throw new Error('I crashed!');
}
then your error will be caught by the error boundary, and you can process it and display a graceful message.
If we change the code like this:
async componentDidMount()
{
throw new Error('I crashed!');
}
which is equivalent to this:
componentDidMount()
{
return Promise.reject(new Error('I crashed!'));
}
then your error will be silently swallowed. Shame on you, React...
So, how do we process errors than? The only way seems to be explicit catch like this:
async componentDidMount()
{
try
{
await myAsyncFunction();
}
catch(error)
{
//...
}
}
or like this:
componentDidMount()
{
myAsyncFunction()
.catch(()=>
{
//...
});
}
If we still want our error to reach the error boundary, I can think about the following trick:
Catch the error, make the error handler change the component state
If the state indicates an error, throw it from the render method
Example:
class BuggyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
}
buggyAsyncfunction() { return Promise.reject(new Error('I crashed async!'));}
async componentDidMount() {
try
{
await this.buggyAsyncfunction();
}
catch(error)
{
this.setState({error: error});
}
}
render() {
if(this.state.error)
throw this.state.error;
return <h1>I am OK</h1>;
}
}
Your code is fine and very readable to me. See this Dale Jefferson's article where he shows an async componentDidMount example and looks really good as well.
But some people would say that a person reading the code may assume that React does something with the returned promise.
So the interpretation of this code and if it is a good practice or not is very personal.
If you want another solution, you could use promises. For example:
componentDidMount() {
fetch(this.getAuth())
.then(auth => {
if (auth) this.checkAuth(auth)
})
}
When you use componentDidMount without async keyword, the doc say this:
You may call setState() immediately in componentDidMount(). It will trigger an extra rendering, but it will happen before the browser updates the screen.
If you use async componentDidMount you will loose this ability: another render will happen AFTER the browser update the screen. But imo, if you are thinking about using async, such as fetching data, you can not avoid the browser will update the screen twice. In another world, it is not possible to PAUSE componentDidMount before browser update the screen
I think it's fine as long as you know what you're doing. But it can be confusing because async componentDidMount() can still be running after componentWillUnmount has run and the component has unmounted.
You may also want to start both synchronous and asynchronous tasks inside componentDidMount. If componentDidMount was async, you would have to put all the synchronous code before the first await. It might not be obvious to someone that the code before the first await runs synchronously. In this case, I would probably keep componentDidMount synchronous but have it call sync and async methods.
Whether you choose async componentDidMount() vs sync componentDidMount() calling async methods, you have to make sure you clean up any listeners or async methods that may still be running when the component unmounts.
Update:
(My build: React 16, Webpack 4, Babel 7):
When using Babel 7 you'll discover:
Using this pattern...
async componentDidMount() {
try {
const res = await fetch(config.discover.url);
const data = await res.json();
console.log(data);
} catch(e) {
console.error(e);
}
}
you will run into the following error...
Uncaught ReferenceError: regeneratorRuntime is not defined
In this case you will need to install babel-plugin-transform-runtime
https://babeljs.io/docs/en/babel-plugin-transform-runtime.html
If for some reason you do not wish to install the above package (babel-plugin-transform-runtime) then you will want to stick to the Promise pattern...
componentDidMount() {
fetch(config.discover.url)
.then(res => res.json())
.then(data => {
console.log(data);
})
.catch(err => console.error(err));
}
I like to use something like this
componentDidMount(){
const result = makeResquest()
}
async makeRequest(){
const res = await fetch(url);
const data = await res.json();
return data
}
Actually, async loading in ComponentDidMount is a recommended design pattern as React moves away from legacy lifecycle methods (componentWillMount, componentWillReceiveProps, componentWillUpdate) and on to Async Rendering.
This blog post is very helpful in explaining why this is safe and providing examples for async loading in ComponentDidMount:
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
To Tag on to #C-F's answer, I added a typescript decorateor
(AsyncMethodErrorHandler) to handle errors in async componentDidMount() and other async methods that fail to bubble up errors to
the application state.
I found this easier than wrapping dozens of async methods in a try/catch block in an app
whose maintainince I inherited.
class BuggyComponent extends React.Component<{error_message?:string}> {
#AsyncMethodErrorHandler("error_message")
async componentDidMount() {
await things_that_might_fail();
}
render(){
if(this.state.error_message){
return <p>Something went wrong: {this.state.error_message}</p>
}
}
}
function AsyncMethodErrorHandler(
/* Key in the this.state to store error messages*/
key: string,
/* function for transforming the error into the value stored in this.state[key] */
error_handler: string | { (e: Error): string } = (e: Error) => e.message
) {
return function (
cls: React.Component,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const f: { (...args: any[]): Promise<any> } = descriptor.value;
return {
...descriptor,
value: function (...args: any[]) {
return f.apply(this, args).catch((e: Error) => {
console.log(`an error occured in the ${propertyKey} Method:`, e);
(this as any as React.Component).setState({
[key]:
typeof error_handler === "string"
? error_handler
: error_handler(e),
});
});
},
};
};
}
Note that as of this writing, this solution does not work for async function properites because:
property decorator[s] can only be used to observe that a property of a
specific name has been declared for a class

Resources