Can I get an example of how to cancel a MobX flow? - reactjs

Mobx official documentation states that you can call cancel on the returned promise from a flow. https://mobx.js.org/best/actions.html There are just no examples of how to do it.
The context:
Call async action within componentDidMount, we need to cancel this action within componentWillUnmount. Want to also setState saying the ui can render after the promise is resolved.
componentDidMount() {
this._fetchRawEguide = this.props.combinedEguide.fetchRawEguide(null, true)
.then(() => {
this._fetchRawEguide = null;
this.setState({
loaded: true
});
})
}
componentWillUnmount() {
if (this._fetchRawEguide) {
this._fetchRawEguide.cancel();
}
}
The Mobx action looks something like this
#action
fetchRawEguide = flow(function*(date, redirectOnError = false) {
try {
const res = yield request(...);
Running into the issue where it says .cancel() doesn't exist when it tries to call it.
I've tried to use when() with regular await / async, it didn't seem to work. If someone has an example for await / async that would be great.

This seemed to work
this._fetchRawEguide = this.props.combinedEguide.fetchRawEguide(null, true);
this._fetchRawEguide.then(() => {
this._fetchRawEguide = null;
this.setState({
loaded: true
});
});
I think applying .then to the promise returned by flow may be casting it and removing the .cancel function

Related

Axios request returning promise Object

Not sure why when I log out the price after executing the fetchPrice() method, I am returned a promise object instead of price. Please help :)
async function fetchPrice() {
await axios
.get(assetData(selectedAsset))
.then((response) => {
return response.data.market_data.current_price.eur;
})
.catch((error) => console.log(error.response.data.error));
}
useEffect(() => {
let price = fetchPrice();
console.log("Asset Price: " + price);
let assetTotal = Number(cost) / Number(price);
setAssetAmount(assetTotal);
}, [selectedAsset]);
The problem is how you are handling the await of your function. Normally when working with promises you use either await or a callback, not both. Because await already wait for the promise to resolve or throw an error, you directly use the return inside the function and not in the callback.
async function fetchPrice() {
try {
const price = await axios.get(assetData(selectedAsset));
return price;
} catch (e) {
console.log(error.response.data.error)
}
}
Using return inside the callback doesn't return the object you expect since it is the result of your callback function and not the original function.
Since fetchPrice is an async function it is normal that if you try to print it you will obtain a promise. This won't change even if you correct your function as I told you above. The only way to obtain the object is by awaiting your fetch price inside the useEffect (and by making the useEffect itself async) like this:
useEffect(async () => {
let price = await fetchPrice();
console.log("Asset Price: " + price);
let assetTotal = Number(cost) / Number(price);
setAssetAmount(assetTotal);
}, [selectedAsset]);
However, if you do it, you will obtain the following warning because your use effect use should be synchronous:
Effect callbacks are synchronous to prevent race conditions. Put the async function inside
The final solution? Set state directly inside the callback.
async function fetchPrice(cost) {
try {
const price = await axios.get(assetData(selectedAsset));
setAssetAmount(Number(cost)/Number(price))
} catch (e) {
console.log(error.response.data.error)
}
}
However, be careful of memory leaks when setting a state asynchronously inside a component that can be unmounted.
You need to use either .then() or async/await.
Example of then()
useEffect(() => {
axios.get("https://api.github.com/users/mapbox").then((response) => {
console.log(response.data);
});
}, []);
In above code, we are using .then() so then will be fired when api call is done.
Example of async/await
async function axiosTest() {
const response = await axios.get("https://api.github.com/users/mapbox");
console.log(response.data, "with await");
}
In above code, we are using await to wait until the api call is done.
Here is the Codesandbox to check the difference with live api call

React Native Why is my code executing before finishing the task? Promise.all().then() asynchronous issues

I have this set up where it runs through an array, and saves it into the phone, but it opens the googleUrl before all the media files are downloaded. Shouldn't the Promise.all() take care of this? Isn't it supposed to wait for mapMediaArray to be finished, and .then() the rest of the work?
const mapMediaArray = selectedMedia.map(index => {
let cleanUrl = `${index.mediaUrl.split('?')[0]}`;
let extension = cleanUrl.split('.').pop();
RNFetchBlob.config({
fileCache: true,
appendExt: extension,
})
.fetch('GET', index.mediaUrl)
.then(res => {
CameraRoll.saveToCameraRoll(res.path());
});
});
Promise.all(mapMediaArray).then(() => {
Linking.openURL(googleUrl);
});
It is true that you call a promise inside your method but your method is not a promise. It a synchronous method so as soon as all synchronous codes gets called promise.all() is called. Your method must be something like this to be identified as a valid promise.
const mapMediaArray = selectedMedia.map(index => {
return new Promise((resolve)=>{
let cleanUrl = `${index.mediaUrl.split('?')[0]}`;
let extension = cleanUrl.split('.').pop();
RNFetchBlob.config({
fileCache: true,
appendExt: extension
})
.fetch('GET', index.mediaUrl)
.then(res => {
CameraRoll.saveToCameraRoll(res.path());
resolve()
})
})
}
Like above code, you have to return a promise and then call resolve in order to make Promise.all work correctly.

Jest test does not resolve a promise as it should. Is there a problem with my syntax or logic?

I am trying to test that a promise resolves and sets state properly according to what the asynchronous call returns. I have been trying to figure out this conundrum for days to almost no avail.
I have tried everything from runAllTimers() to runOnlyPendingTimers() to setTimeout() to simulate the time for the asynchronous call to finish. However, I didn't want to rely on setTimeout(), as that depends on real time instead of fake timers. While the test technically "passes" with setTimeout, I'm not so sure it's valid because when I console-log the state after the setTimeout, it does not update as it should and remains the same after I await the asynchronous call.
import 'item' from '../getItem'
jest.useFakeTimers();
it('should update state after fetchItemData fires', async () => {
const locationOfItem = {
pathname: '/item/path',
search: `?id=${itemId}`,
};
const props = {
someStore,
locationOfItem,
};
// The true is here because I use mobx for the store
const wrapper = createWrapper(ItemDataPage, props, true);
getItem.mockImplementationOnce(() =>
Promise.resolve({ item }),
);
await wrapper.fetchItemData();
// jest.runAllTimers();
// jest.runOnlyPendingTimers();
expect(getItem).toHaveBeenCalled();
// setTimeout(() => {
expect(wrapper.state.isLoading).toBe(false);
expect(wrapper.state.hasError).toBe(false);
expect(wrapper.state.item).toEqual({ item });
// }, 1000);
});
fetchItemData = async () => {
const { locationOfItem } = this.props;
const { search } = locationOfItem;
const id = search.replace('?id=', '');
try {
const item = await getItem(id);
} catch {
this.state.hasError = true;
} finally {
this.setState({isLoading: false, hasError: false, item,});
}
};
The expected results are that all assertions should be as stated in the code snippet. However, when I run this in any context outside of setTimeout(), it fails. Specifically, while getItem gets called properly, and hasError's state is false, the state of isLoading does not get set to false as it should after the promise resolves, and the state of item does not go from an empty object to a populated object as it should. In the method I'm testing, namely fetchItemData(), the method calls a service and sets the state of item to whatever data the service returns, which is an object. The original state of item is always an empty object. In short, isLoading remains true, and item remains an empty object. There is an error message saying "cannot read property "isInStock" of undefined. isInStock is a key inside of the item object, so I would expect that this property is not yet defined because item is still an empty object and does not get updated.
When testing react components, don't test methods. Test the desired implementations inside.
componentDidMount automatically runs once you create your wrapper. If fetchItemData is run inside componentDidMount, the test will do it automatically.
Mock the api call. Personally, I find it easier to understand when setting a return using mockMeturnValue or mockReturnValueOnce, as opposed to mockImplementation. Ultimately, at your discretion
// api test setup
jest.mock('./api/apiFile') // mocks every function in file
const getItem = jest.fn()
getItem.mockImplementationOnce(() =>
Promise.resolve({ item }),
);
const wrapper = createWrapper(ItemDataPage, props, true);
expect(getItem).toHaveBeenCalled();
expect(wrapper.state.isLoading).toBe(false);
expect(wrapper.state.hasError).toBe(false);
expect(wrapper.state.item).toEqual({ item });
You need to mock const getItem = jest.fn() before you create your wrapper.

Call Axios, then call separate Axios call for each returned item

I got my Axios calls to update, but request is returned to my reducer. The problem is that the first axios call updates my state, then my second group of promises does not update my component immediately.
export function selectRoutingBooks(routing) {
const url = `${ROOT_ROUTINGS_URL}/${routing.RoutingId}/${routing.VersionId}/books`;
let promises = [];
const request = axios.get(url).then((response)=>{
response.data.map((item)=>{
const url2 = `${ROOT_ITEMS_URL}/${item.MatItemKey}?all=true`;
promises.push(axios.get(url2));
});
axios.all(promises).then(function(results) {
results.forEach(function(res) {
let common = response.data.find(x=>x.MatItemKey === res.data.base.ItemKey);
let newCommon = {...common, ...res.data};
var i = response.data.findIndex(o => o.MatItemKey === newCommon.base.ItemKey);
if (response.data[i]) { response.data[i] = newCommon }
})
});
return response
});
return {
type: ROUTING_BOOKS,
payload: request
}
}
I am not entirely sure how to get all of my promises to update my state at the same time. When I check in
componentWillUpdate(){
console.log("Component will update RoutingDetail", this.props.routingBooks)
}
I am getting all the data, but the props don't update here.
TIA
I found a solution to my problem. I needed to return the Promise.all back to the reducer.
return Promise.all(promises).then(function(results) {
instead of
Promise.all(promises).then(function(results) {
This updates my reducer when the promise.all is fulfilled.
I think you should be using Promise.all() for what you want to do or maybe async await if you are using ES7.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

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