AsyncStorage setItem inside loop - react-native

Is it ideal to setItem inside a loop? Assuming that i retrieved a list of data from the backend, which i need to do some heavy processing on each data before i can store it individually inside AsyncStorage store.
import { AsyncStorage } from "react-native";
async function getList(query){
/* some call setup in here */
try {
const response = await fetch(/* call parameters in here */);
const { data, err } = response;
if (err) throw new Error(err.message);
await processAndStoreItems(data);
return data;
} catch(err){
throw err;
}
}
async function processAndStoreItems(data){
/* data is a list of array that needs to be processed first before storing */
data.forEach( d => {
/* Some processing of data happening in here, and it may be a heavy processing that may take time */
const { processedKey, processedData } = await heavyProcessingHere(d);
await AsyncStorage.setItem(processedKey, JSON.stringify(processedData));
});
}
The reason why i did this was because, some of the process may take longer time, and the idea to just wait the whole process to finish then only store isn't ideal, since there might be case where user will logout from the app while the whole process is still running, and i need to clear the stored items upon logout.
If i do setItem inside loop, at least i can be assured that the store will always be updated for any completed processed data, and i can just delete whatever that is already stored on logout, rather than waiting for the whole process to complete, then only store it in AsyncStorage, which will be a problem if the user logout while the process is still incomplete, leaving the store outdated and making deletion hard for those items that are still in process.
So i need to know if performance wise, using setItem inside loop is okay? I read from somewhere about people facing crash issue with AsyncStorage when attempting to use setItem inside loop, which is bad for me.

Why you not putting your array to cookie
Like this;
await AsyncStorage.setItem(dataArray, JSON.stringify(data));

Related

Can lodash debounce run at least once while it's being debounced?

I am debouncing a method and wanted to know if it's possible to process it at least once while it's actively being debounced.
Example: The debounce wait is set to 1 second, and during 10 seconds straight, the function is being called actively (more often than 1 second, resulting in the function still not invoked yet). Could I at least run the function once say at the 5th second? So I do not have to wait till 10s fully pass before it runs finally once.
My fear is that if the process is "busy" and constantly bombarded by requests, I would still like to process something.
Not sure if it's relevant, but this is part of React Native.
Debouncing partially blocks unnecessary requests but does not guarantee that new requests will not be dispatched while the system is busy.
We should implement a mechanism to detect if the system is busy or not then we decide to dispatch a new request.
import debounce from "lodash/debounce";
function Component() {
// Detect the status of an expensive operation
let operationRunning = false;
const runExpensiveOperation = () => {
// If expensive operation still running, discard dispatching
// new operations
if (operationRunning) {
return;
}
operationRunning = true;
/** Do expensive operations here - Writing, reading
from database or compress files,... **/
operationRunning = false;
};
const debounceExpensiveOperation = debounce(runExpensiveOperation, 1000);
// ássign debounced function for the event handler
}

Vuex dispatch creates significant overhead

Recently I noticed that dispatching a certain action takes way longer than it should, so I went to add some timers.
This is the action code:
// ...
mutations: {
setPrimary(state, data) {
state.resources.primary = data
}
},
// ...
actions: {
async load({ commit }) {
const start = performance.now()
// ...
const apiResult = await apiClient.get("/")
const startCommit = performance.now()
commit("setPrimary", apiResult)
console.log(`commit finished in ${performance.now() - startCommit} ms`)
console.log(`action finished in ${performance.now() - start} ms`)
}
}
And the code dispatching the action:
const start = performance.now()
await store.dispatch("load")
console.log(`dispatch finished in ${performance.now() - start} ms`)
Now the expected output would be that the timings are pretty close, but the opposite is the case:
action finished in 820
dispatch finished in 1365 ms
Which to me is quite confusing, since calling dispatch shouldn't add such massive overhead. So far I have no idea where it might come from.
Edit:
It turns out this is caused by the commit call. The delay disappers without this.
Timing the commit like so reveals that it only takes 1 ms to execute, which means that this somehow has side effects I am unaware of.
The overhead comes from the fact that your action is an "asynchronous" function which means when the "interpreter" executes it, it will through it to a "browser API" to handle its execution instead of executing it directly in the "Stack", and wait for its result and after that, will get it back to the "queue" where the "Event loop" will wait for the stack to be empty and returns it with the results to the stack to continue execution

Vuex mutations and actions responsibilities

Except that a mutation must be synchronous whereas an action can contain asynchronous code, I find it difficult to establish some guidelines on how to correctly choose where the logic is supposed to be implemented.
The Vuex doc describes the mutations as transactions, which makes me think that each mutation must ensure as much as possible state coherence, potentially doing multiple changes in the store within the same mutation.
Let’s take for example the following state, storing documents that can either be loading or loaded:
const state = {
loading: []
loaded: []
}
const mutations = {
setLoaded(state, doc) {
state.loaded.push(doc)
// Remove doc from loading:
const index = state.loading.find(loadingDoc => doc.id === loadingDoc.id)
if (index !== -1) {
state.loading.splice(index, 1)
}
}
}
Here the mutation ensures that the doc is either in loading or loaded state, not both. The problem doing so is that the mutations could eventually end up having a lot of logic, which could make them more difficult to debug through the Vuex dev tools.
Some prefer to keep the mutations as simple as possible (eg. only one state change per mutation), but then it’s up to the actions (or the components in case they directly call mutations) to ensure state coherence, which could be more error-prone since, in the above example, you would need to remove the doc from loading state each time you call the “setLoaded” mutation.
As for data validation, unluckily we have no simple way to do it in mutations since mutations cannot return anything. So an action calling a mutation has no way to know that the data is invalid. Eg.:
const mutations = {
setValue(state, intValue) {
if (intValue < 0) {
// error
return
}
state.value = intValue;
}
}
Thus, it seems that data validation needs to be done in actions, with mutations assuming that the data they receive are correct.
What are your thoughts about this, and more generally about actions and mutations? Do you have guidelines on how to properly use them from your own experience?
Data validation MUST be done in actions if it encompasses multiple mutations and/or needs to signal to the caller if the operation failed.
My rule (and this will be argued...) is to always use async CALLS (with async/await) whenever possible, even if I am calling sync methods. Then my code is future proof if the sync method becomes async. And in the case you mentioned, it also allows a return value and/or error from Vuex.
let response
response = await anAsyncMethod()
response = await aSyncMethod()
response = await aVuexMutateOp()
response = await aVueActionOp()
Yes this adds a bit of processing overhead, but it is also just as clear by using async/await syntax. It streamlines the error processing as well, routing it to a catch block.
Because mutations have the restriction that they MUST be synchronous AND CANNOT call other mutations AND DO NOT return a value demonstrates that the mutate should only be used in the simplest of situations.

RN with Firestore unable to wait for Promise to resolve

I have a simple call to Firestore to write a doc and then wait for the doc to finish writing before changing state of the parent. However, the parent state is being changed too fast, resulting in reading fields that I think have not yet been written/propagated. I tried adding a delay with setTimeout and it seems ignored. How can I make sure the state change is absolutely only called after the Firestore doc is written completely?
The code:
updateDBEntry(stateObj) {
var that = this;
var docRef = firebase.firestore().collection('sessions').doc(this.state.userID);
docRef.get().then((doc) => {
if (!doc.exists) {
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
var duration = (stateObj.seshDuration) ? stateObj.seshDuration : 1;
docRef.set({
seshName: stateObj.seshName,
seshStreet: stateObj.seshStreet,
seshZipcode: stateObj.seshZipcode,
seshDuration: duration,
seshDesc: stateObj.seshDesc,
seshTime: timestamp,
}).then(() => {
var handleToUpdate = that.props.handleToUpdate;
setTimeout(() => {
handleToUpdate(1); //this changes the parent's state
}, 10000);
});
}
});
}
I'm not sure exactly the problem you're running into here, mostly because you've only shown this one function, and not how you're using it in the rest of your app. But I can tell you three things for sure:
When the promise from set() resolves successfully, you can be certain the document is written.
get() and set() are asynchronous, and so is then(). They all return promises the represent async work.
Item 2 means that your entire function updateDBEntry() is also asynchronous and returns immediately, before any of the work is complete.
Because this entire function is async, when it returns, the document will not have been created yet. Instead, maybe this function should return that resolves only after all the work is complete, so that the caller can also use it set up some code to execute after the work is done.

How to initialize MobX store with async data (AsyncStorage) in React Native

We created a simple MobX store to save an object. This MobX store also syncs with AsyncStorage every time fooObject is changed:
import { observable } from 'mobx';
class FooStore {
#observable fooObject = {};
setFooObject(newFooObject) {
this.fooObject = newFooObject;
this.syncWithAsyncStorage();
}
syncWithAsyncStorage() {
AsyncStorage.setItem('fooObject', JSON.stringify(this.fooObject));
}
This mobx store is then consumed by various screens in the app.
As you can see, we use RN AsyncStorage, which is kind of a LocalStorage but for native apps, to always store the current state of fooObject.
The question is: when the app is closed, we loose FooStore state, so we want to retrieve the last 'save' we did to AsyncStorage (which is persisted).
The problem is AsyncStorage is (as the name states) an ASYNC function, and we can't wait for a promise resolution in FooStore's constructor.
How one would accomplish this initial state initialization of this MobX store based on the resolved return value of AsyncStorage?
We thought about writing an initialization method (async method) in FooStore like this:
async initializeFromAsyncStorage() {
this.fooObject = await JSON.parse(await AsyncStorage.getItem('fooObject'));
}
And then call this method on app initialization in our root App.js.
But I'm not sure if this would present unintended side effects and if this is idiomatic mobx/react-native.
Yes, I would create an initialization method. If the instance is not dependent of the initial data, you could call it right after the instantiation:
const fooStore = new FooStore()
fooStore.initializeFromAsyncStorage()
export default fooStore
Thill will not guarantee that the initial data is loaded when the app is constructed, but since the data is an optional observable, your app will react to it if/when it’s available.
Remember that AsyncStorage.getItem will resolve null if it has not been set, so you might want to do:
async initializeFromAsyncStorage() {
const result = await AsyncStorage.getItem('fooObject')
if (result !== null) {
this.fooObject = JSON.parse(result)
}
}