I can get list with get axios.get method. then ı can use setState and it works flawlessly.
but the return is not true , its return undefined console.log(result) => undefined . How can ı check if setState work fine return true or return false ?
getList = async () => {
await axios.get("http://localhost:5000/list").then((response) => {
this.setState(
{
copy: response.data.list,
},
() => {
return true;
},
);
});
};
componentDidMount = () => {
this.getList().then((result) => {
console.log(result);
});
};
Your return true statement is in setState's post-set callback. It won't be propagated to the promise that's returned from getList; indeed, you return nothing from that function (you don't even return the Axios promise; if you did return that, you would get the response logged in your console.log, but it would be logged before the setState callback finished), so you get undefined in the console.log.
If you need getList to return a promise that resolves to true once the state has been set, you'll need
getList = () => {
return new Promise((resolve) =>
axios.get("http://localhost:5000/list").then((response) =>
this.setState(
{
copy: response.data.list,
},
() => resolve(true),
),
),
);
};
componentDidMount = () => {
this.getList().then((result) => {
console.log(result);
});
};
The second argument to set state can return true but, it's not going anywhere. You need to somehow use it, example:
const response = await axios.get("http://localhost:5000/list")
return new Promise((resolve) => {
this.setState({
copy : response.data.list
}, () => {
resolve(true);
})
})
now the chain will work because you are resolving the promise chain with true instead of returning it from the callback function
Related
Trying to pass a reference to the recursive function to check if Redux action data fetch is complete, but getting function reference errors
const fetchAccountComplete = (state, accountNo) => { //state here is function reference
return new Promise(resolve => {
(function waitForFetchComplete(state, accountNo) {
const {isFetching, receivedAt} = state().account[accountNo] // getting state not a function here
if (!isFetching) return resolve()
setTimeout(waitForFetchComplete, 100)
})()
})
}
Is there a better way to return a promise to the caller function in Redux dispatch actions so that once the data is fetched, i need to do some other logic in other action.
Update 1:
should have been more clearer. There are two callers of this Request, Recieve actions on say Account data. First caller is directed similar to the above comment by you so waits until complete, second caller would not be doing the async call again and need to check if data fetch is complete so trying to see if recursive function with check on state so that promise can be resolved is being done
You could take advantage of promising chaining.
Example:
Have three actions like: IS_FETCHING, FETCH_SUCCESS, FETCH_ERROR.
IS_FETCHING:
Will simply set your state as pending (may be useful for showing a loading animation, for example).
FETCH_SUCCESS:
Will contain the result of the fetch to update the state. Will also clear the isUpdating flag
FETCH_ERROR:
Will contain any possible error due to the fetch (application or network error). Will also clear the isUpdating flag
Then, what you could do at application level is:
dispatch({type: IS_FETCHING, payload: data});
fetch(`https://MY-SERVER.com/?data=${data}`)
.then(response => response.json())
.then(json =>
dispatch({
type: isError(json) ? FETCH_RESULT : FETCH_ERROR,
payload: json
})
);
You could even benefit of action creators for the job.
Here is a good guide for that: https://redux.js.org/advanced/async-actions
If you have a function that returns a promise that is called multiple times with the same arguments then you can group that in a way so that the function is not called when it still has an unresolved promise and something tries to call it again with the same arguments.
Here is an example:
//group promise returning function
const createGroup = (cache) => (
fn,
getKey = (...x) => JSON.stringify(x)
) => (...args) => {
const key = getKey(args);
let result = cache.get(key);
if (result) {
return result;
}
//no cache
result = Promise.resolve(fn.apply(null, args)).then(
(r) => {
cache.done(key); //tell cache promise is done
return r;
},
(e) => {
cache.done(key); //tell cache promise is done
return Promise.reject(e);
}
);
cache.set(key, result);
return result;
};
//creates a cache that will remove cached value when
// Promise is done (resolved or rejected)
const createCache = (cache = new Map()) => {
return {
get: (key) => cache.get(key),
set: (key, value) => cache.set(key, value),
done: (key) => cache.delete(key),
};
};
//function that retuns a promise
const later = (time, value) => {
console.log('executing later with values', time, value);
return new Promise((r) =>
setTimeout(() => r(value), time)
);
};
//create group function with a cache that will remove
// cache key when promise is resolved or rejected
const groupAndRemoveCacheOnDone = createGroup(
createCache()
);
//grouped version of the later function
const groupedLater = groupAndRemoveCacheOnDone(later);
//testing the groped later
groupedLater(100, 8); //first call causes console.log
groupedLater(100, 8); //same arguments will not call later
groupedLater(100, 8); //will not call later
//will call later because arguments are not the same
// as the other calls
groupedLater(100, 'XX');
groupedLater(100, 8) //will not call later
.then((value) => {
console.log('resolved with:', value);
//this will call later because cache value is removed
// after promise is resolved
return groupedLater(100, 8);
})
.then(() => {
//testing with fetchAccountComplete
console.log(
'***** how many times is fetchAccountComplete called *****'
);
const fetchAccountComplete = (state, accountNo) => {
console.log(
'fetchAccountComplete called with',
accountNo
);
return new Promise((resolve) => {
(function waitForFetchComplete(state, accountNo) {
const {
isFetching,
receivedAt,
} = state().account[accountNo]; // getting state not a function here
if (!isFetching) return resolve();
setTimeout(
() => waitForFetchComplete(state, accountNo),
100
);
})(state, accountNo);
});
};
const data = {
account: [{ isFetching: true }],
};
const state = () => data;
const groupedFetchAccountComplete = groupAndRemoveCacheOnDone(
fetchAccountComplete
);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0).then((resolve) =>
console.log('resolved')
);
data.account[0].isFetching = false;
});
I'm new in React and I was looking to achieve this kind of flow:
// set the state
// execute a function `f` (an async one, which returns a promise)
// set the state again
// return the promise value from the previous function
So, what I'm doing now is the following:
async function handleSomething() {
this.setState((prevState) => { ... },
() => {
let result = await f()
this.setState((prevState) => { ... },
...
)
})
return result;
}
Hope you get the idea of what I want to achieve. Basically I want to get result, which is the value returned from awaiting f, and return it in handleSomething so I can use it in another place, but wrapping it up inside those setState calls:
// g()
// setState
// res = f()
// setState
// return res
My question is, how can I do this properly? Maybe should I modify the state with the result value and get it from there?.
EDIT:
Usage of handleSomething:
// inside some async function
let result = await handleSomething()
You can create a Promise that resolves once both setState calls are done:
function handleSomething() {
return new Promise(resolve => {
this.setState(
prevState => {
/*...*/
},
async () => {
let result = await f();
this.setState(
prevState => {
/*...*/
},
() => resolve(result)
// ^^^^^^^ resolve the promise with the final result
);
}
);
});
}
Which would be used like:
this.handleSomething().then(result => /* ... */)
// or
const result = await this.handleSomething();
I made my componentWillMount() async. Now I can using await with the setState.
Here is the sample code:
componentWillMount = async() => {
const { fetchRooms } = this.props
await this.setState({ })
fetchRooms()
}
So question here is this.setState returns promise because I can use await with it?
Edit
When I put await then it runs in a sequence 1, 2, 3 And when I remove await then it runs 1, 3, 2??
componentWillMount = async() => {
const { fetchRooms } = this.props
console.log(1)
await this.setState({ } => {
console.log(2)
})
console.log(3)
fetchRooms()
}
You can promisify this.setState so that you can use the React API as a promise. This is how I got it to work:
class LyricsGrid extends Component {
setAsyncState = (newState) =>
new Promise((resolve) => this.setState(newState, resolve));
Later, I call this.setAsyncState using the standard Promise API:
this.setAsyncState({ lyricsCorpus, matrix, count })
.then(foo1)
.then(foo2)
.catch(err => console.error(err))
setState is usually not used with promises because there's rarely such need. If the method that is called after state update (fetchRooms) relies on updated state (roomId), it could access it in another way, e.g. as a parameter.
setState uses callbacks and doesn't return a promise. Since this is rarely needed, creating a promise that is not used would result in overhead.
In order to return a promise, setState can be promisified, as suggested in this answer.
Posted code works with await because it's a hack. await ... is syntactic sugar for Promise.resolve(...).then(...). await produces one-tick delay that allows to evaluate next line after state update was completed, this allows to evaluate the code in intended order. This is same as:
this.setState({ roomId: room && room.roomId ? room.roomId : 0 }, () => {
console.log(2)
})
setTimeout(() => {
console.log(3)
});
There's no guarantee that the order will stay same under different conditions. Also, first setState callback isn't a proper place to check whether a state was updated, this is what second callback is for.
setState does not return a promise.
setState has a callback.
this.setState({
...this.state,
key: value,
}, () => {
//finished
});
It does not return a promise.
You can slap the await keyword in front of any expression. It has no effect if that expression doesn't evaluate to a promise.
setState accepts a callback.
Don't think setState is returning a Promise but you can always do this
await new Promise( ( resolve ) =>
this.setState( {
data:null,
}, resolve )
)
or you can make some utility function like this
const setStateAsync = ( obj, state ) => {
return new Promise( ( resolve ) =>
obj.setState( state , resolve )
)
}
and use it inside a React.Component like this:
await setStateAsync(this,{some:'any'})
You can simple customize a Promise for setState
componentWillMount = async () => {
console.log(1);
await this.setRooms();
console.log(3);
};
setRooms = () => {
const { fetchRooms } = this.props;
return fetchRooms().then(({ room }) => {
this.setState({ roomId: room && room.roomId ? room.roomId : 0 }, _ =>
console.log(2)
);
});
};
Or
setRooms = async () => {
const { fetchRooms } = this.props;
const { room } = await fetchRooms();
return new Promise(resolve => {
this.setState({ roomId: room && room.roomId ? room.roomId : 0 }, _ =>
resolve()
);
});
};
Hope this help =D
Simpler way to answer this question is we can use promisify function present in pre-installed util library of node.js and then use it with the await keyword.
import {promisify} from 'util';
updateState = promisify(this.setState);
await this.updateState({ image: data });
I have 3 API calls in a function fetchData. How do I set the loading state to false after executing the following code:
componentWillMount() {
this.setState({ loading: true })
this.fetchData()
//after these two calls, I want to set the loading State to false
}
All the API calls are each a promise
Using React 14.3
If you're using Redux you could have your reducer set loading: false after the API calls return.
See for more info
You should make the API calls on componentDidMount as componentWillMount is set to be deprecated.
FetchData() {
APIcalls.then(() => {
This.setState({ loading: false })
})
}
You would make use of async-await and use componentDidMount instead of componentWillMount since this method is likely to be replaced in react v17
Also check this question
Use componentWillMount or componentDidMount lifecycle functions for async request in React
async componentDidMount() {
this.setState({ loading: true })
await this.fetchData()
this.setState({ loading: false });
}
async fetchData() {
await ApiCall1();
await ApiCall2();
await ApiCall3();
}
I used es6 async await function. Inside that, You can use Promise.all to resolve the all promises.
fetchData = async () => {
const a = new Promise((resolve, reject) => {
setTimeout(() => { console.log('one'); return resolve("done!"); }, 1000)
});
const b = new Promise((resolve, reject) => {
setTimeout(() => { console.log('two'); return resolve("done!"); }, 2000)
});
return await Promise.all([a, b]) ? true : false;
}
Write the logic in componentDidMount instead of componentWillMount. you can use fetchData function as a
promise. After that you can set state whatever you want.
componentDidMount() {
this.setState({ loading: true })
this.fetchData().then((result)=>{
console.log(result, 'three');
this.setState({ loading: false });
});
}
You can see working example click here
I'm using a class to do do some database stuff. In this example I want to reset the data and return the data.
export default class Db {
constructor () {
this.connection = monk('localhost:27017/db')
}
async resetDB () {
const Content = this.connection.get('content')
await Content.remove({})
await createContent()
return Content.findOne({ title: 'article' })
}
}
In my test I'm calling the db.resetDB(), but I need to get the returned value, as I need to pass the ID as parameter.
But how do I do that? I think my problem is, that this is asynchronous.
let id
describe('Test', () => {
before(() => {
db.resetDB(res => {
id = res._id
Article.open(id) // How do I get the ID?? I do get undefined here
Article.title.waitForVisible()
})
})
it('should do something', () => {
// ...
})
})
When the async function is called, it returns a Promise. hence you can get the return value in .then() of the promise. You can do it something like this,
let id
describe('Test', () => {
before(() => {
db.resetDB().then(res => {
id = res._id
Article.open(id) // How do I get the ID?? I do get undefined here
Article.title.waitForVisible()
})
})
it('should do something', () => {
// ...
})
})
You can make the before function to wait until all asynch calls gets finished by using done() callback.
https://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support
What you can do is
let id
describe('Test', () => {
before((done) => {
db.resetDB().then(res => {
id = res._id
Article.open(id) // How do I get the ID?? I do get undefined here
Article.title.waitForVisible()
done()
})
})
it('should do something', () => {
// ...
})
})