Loading data into a React Native FlatList - javascript

I fully admit that I'm pretty new to Reactive Native at this point so I anticipate this being an easy answer and a potentially stupid question :) In fact, this may not be a RN issue per se, as I'll explain below.
What I have is the following code:
class MyScreen extends React.Component {
constructor(inProps) {
super(inProps);
this.state = { listData : [ ] };
}
render() { return (
<View>
<FlatList data={ this.state.listData }
renderItem={ ({item}) =>
<Text>{item.firstName} {item.lastName}</Text>
}
/>
</View>
); }
async componentDidMount() {
const data = [ ];
let keys = await AsyncStorage.getAllKeys();
keys.forEach(async function(inKey) {
let obj = await AsyncStorage.getItem(inKey);
obj = JSON.parse(obj);
data.push(obj);
});
this.setState({ listData : data });
};
}
The goal is simply to load the data from AsyncStorage and dump it in the list. Fairly basic it would seem, right? Unfortunately, I can't get it to work: the list is empty.
Now, to cover the stupid things: yes, there is indeed data stored and yes, it's in the correct form (I know this because if I simply grab ONE SPECIFIC item and push() it into data then it appears in the list as expected).
In fact, I know what the problem is: it's that the setState() call is executing before the keys.forEach() call completes. I know this because if I replace the setState() call with this:
setTimeout(function() {
this.setState({ listData : data });
}.bind(this), 1000);
...then suddenly my list is populated as expected (I can also throw some console.log()'s in there and see that the one after the keys.forEach() call actually executes BEFORE those inside the keys.forEach()). I also know that obviously the getItem() call is waiting before the JSON.parse() is called, otherwise this wouldn't work at all.
In other words: I know this is caused by the asynchronous nature of this code. What I can't seem to figure out is how to write it properly to avoid this.
I'm looking at it and I'm thinking "well, keys.forEach() is a blocking call, so how could it possibly not complete before the setState() call, ESPECIALLY given the async usage on the keys.forEach() callback AND the await usage on the getItem() call inside?"... the only thought I had was to drop an async in front of the keys.forEach() call, but that doesn't make any difference.
So, it seems to be a basic JavaScript question in essence, but I can't seem to solve it... but on the other hand, I'm not sure if I'm trying to do this in a way that I fundamentally shouldn't be in RN land and maybe THAT'S the real problem.
Note that the data has to be pulled dynamically from AsyncStorage because of the flow through the application (adds are done on another screen), so it seems to me that trying to load it in componentDidMount() makes sense, I'm just getting hung up with the asynchronous nature of the calls involved.
Any suggestions?
Thanks!
EDIT: I should also mention that I know one way I can solve this is to just remove the asynchronous nature entirely: rather than storing individual data items, instead store everything in a single object in AsyncStorage. Then I can read the ONE item, which would be an array (or would contain an array), then the only asynchronous part is the getItem() call, but I can use the callback version of that and make it work that way... and I'm not against that approach at all, but at this point I really want to understand what I'm doing wrong if for no other reason than to learn something because this seems like it SHOULD work :)

keys.forEach execute the code in its own scope (in its own callback function), and that breaks your async / await pair.
Do this instead:
for (let inKey of keys) {
let obj = await AsyncStorage.getItem(inKey);
obj = JSON.parse(obj);
data.push(obj);
}
If you really want forEach, you have to write your own version that returns Promise, so you can hook await on it for example:
await keys.awaitableForEach(async function(inKey) {

Related

Call firebase httpsCallable multiple times

Calling foo two times creates two subscribers and that is not ok but even so the second call of foo seems to get stuck somewhere because console.log gets called only one time.
I know that I should not subscribe in this manner but I really do not see the solution to get myFunc to output the second result
const myFunc = functions.httpsCallable('myFunc')
function foo(inData){
myFunc({data:inData}).subscribe((res)=>{
console.log(res)
})
}
foo('aaaaa')
foo('bbbbb')
second attempt still no luck
myFunc(data){
return functions.httpsCallable('myFunc')(data)
}
async function foo(inData){
let res = await lastValueFrom(myFunc({data:inData}))
console.log(res)
}
foo('aaaaa')
foo('bbbbb')
The HttpsCallable function returns a promise by default. As you can see in this related question , there are a few differences between promises and observables, as it seems that you are already working with observables, I would try to convert the promise received from the function to an observable using this method, as suggested in this other question:
import { from } from 'rxjs';
const observable = from(promise);
Although, there are other methods suggested as well in the same question.
I would also like to point you to the Using observables to pass values, I think you might not have shared how you are creating the observer. I'm just guessing but you should also unsubscribe to clean up data ready for the next subscription. Also, missing next notification that is required.
As an extra, I would say that if you are not obligated to use observables, handle it as a promise.

Knex bulk insert not waiting for it to finish before passing to the next async operation

I am having a problem where I am making a bulk insert of multiple elements into a table, then I immediatly get the last X elements from that table that were recently inserted but when I do that it seems that the elements have not yet been inserted fully even thought I am using async await to wait for the async operations.
I am making a bulk insert like
const createElements = elementsArray => {
return knex
.insert(elementsArray)
.into('elements');
};
Then I have a method to immediately access those X elements that were inserted:
const getLastXInsertedElements = (userId, length, columns=['*']) => {
return knex.select(...columns)
.from('elements').where('userId', userId)
.orderBy('createdAt', 'desc')
.limit(length);
}
And finally after getting those elements I get their ids and save them into another table that makes use of element_id of those recently added elements.
so I have something like:
// A simple helper function that handles promises easily
const handleResponse = (promise, message) => {
return promise
.then(data => ([data, undefined]))
.catch(error => {
if (message) {
throw new Error(`${message}: ${error}`);
} else {
return Promise.resolve([undefined, `${message}: ${error}`])
}
}
);
};
async function service() {
await handleResponse(createElements(list), 'error text'); // insert x elements from the list
const [elements] = await handleResponse(getLastXInsertedElements(userId, list.length), 'error text') // get last x elements that were recently added
await handleResponse(useElementsIdAsForeignKey(listMakingUseOfElementsIds), 'error text'); // Here we use the ids of the elements we got from the last query, but we are not getting them properly for some reason
}
So the problem:
Some times when I execute getLastXInsertedElements it seems that the elements are not yet finished inserting, even thought I am waiting with async/await for it, any ideas why this is? maybe something related to bulk inserts that I don't know of? an important note, all the elements always properly inserted into the table at some point, it just seems like this point is not respected by the promise (async operation that returns success for the knex.insert).
Update 1:
I have tried putting the select after the insert inside a setTimeout of 5 seconds for testing purposes, but the problem seems to persist, that is really weird, seems one would think 5 seconds is enough between the insert and the select to get all the data.
I would like to have all X elements that were just inserted accessible in the select query from getLastXInsertedElements consistently.
Which DB are you using, how big list of data are you inserting? You could also test if you are inserting and getLastXInsertedElements in a transaction if that hides your problem.
Doing those operations in transaction also forces knex to use the same connection for both queries so it might lead to a tracks where is this coming from.
Another trick to force queries to use the same connection would be to set pool's min and max configuration to be 1 (just for testing is parallelism is indeed the problem here).
Also since you have not provided complete reproduction code for this, I'm suspecting there is something else here in the mix which causes this odd behavior. Usually (but not always) this kind of weird cases that shouldn't happen are caused by user error in elsewhere using the library.
I'll update the answer if there is more information provided. Complete reproduction code would be the most important piece of information.
I am not 100% sure but I guess the knex functions do not return promise by default (but a builder object for the query). That builder has a function called then that transforms the builder into a promise. So you may try to add a call to that:
...
limit(length)
.then(x => x); // required to transform to promise
Maybe try debugging the actual type of the returned value. It might happen that this still is not a promise. In this case you may not use async await but need to use the then Syntax because it might not be real js promises but their own implementation.
Also see this issue about standard js promise in knex https://github.com/knex/knex/issues/1588
In theory, it should work.
You say "it seems"... a more clear problem explanation could be helpful.
I can argue the problem is that you have elements.length = list.length - n where n > 0; in your code there are no details about userId property in your list; a possible source of the problem could be that some elements in your list has a no properly set userId property.

Trouble Fetching JSON files on server into Javascript Array

I'm quite new to using Javascript and particularly JSON, and I've been struggling to do this:
There is a JSON file on my web server which I am trying to access and parse into a JavaScript object. What I am trying to do is parse that JSON into an array and then further manipulate that array based on other user variables.
This is a sample of what the JSON looks like:
{"log":
[{
"name":"Al",
"entries":[8,12,16,19]},
{"name":"Steve",
"entries":[11,17,22]}]}
What I need to do is retrieve the array for one of the entries and store it in an array as a JavaScript object. What I have tried to do is this:
var entriesLogged;
fetch ('url to the json file').then(function(response){
return response.json();
}).then(function(data){
entriesLogged = data.log[0].entries;
});
However, I can't seem to get this to work and to assign the value to the variable in a way that persists outside of this scope. I have been able to output the value of the array using console.log, but I have not been able to actually work with and manipulate that data like an object. I'd ideally like to parse the JSON file from the server onto a global array.
Most of the tutorials I've come across so far have used the JSON file to output console logs or change the contents of html elements, however I need to retrieve the values from the JSON into a global array first.
Am I missing something here? Does anyone have advice on how to do this?
Best wishes,
Dom
Are you trying to manipulate the entriesLogged variable after the fetch promise chain? The fetch is asynchronous so this means that any code after the fetch chain will run before the fetch chain finishes.
var entriesLogged;
fetch('url to the json file').then(function(response){
return response.json();
}).then(function(data){
entriesLogged = data.log[0].entries;
});
// Code down here will run before the fetch chain finishes
// So if you're using entriesLogged down here, it will be null
console.log(entriesLogged); // entriesLogged is null
You might want to do something like:
.then(function(data){
entriesLogged = data.log[0].entries;
myFunction(entriesLogged);
});
function myFunction(myData) {
console.log(myData); // can access entriesLogged here
}
What is happening is that the call is async, it means that when you make that call a new thread is created and javascript automatically passes to the next line.
var entriesLogged;
fecth(something).then(function(data){entriesLogged = data[0]}); // this goes to another thread an takes e.g. 5 miliseconds but javascript does not wait for this to complete, it goes to the next line
console.log(entriesLogged); // this is executed before the call is finished
There are TONS of "answers" for variations of this question that don't actually solve the problem, they just describe the issue. (tl;dr solution is last block of code)
The issue is that .fetch('URL.json') and .json() are "asynchronous" calls. So the script will start working on that call, then continue on to the rest of the commands in your script while the .fetch() and .json() calls are still being worked on. If you then hit a command BEFORE one of these asynchronous calls is done, the rest of your script will have no data to work with.
There are tons of places to look for fully understanding the ins and outs of how async calls work in JS, for example here:
How do I return the response from an asynchronous call?
If you're like me and you just want the code to function synchronously, the quick and dirty solution is to create an async main loop and then to make sure you use the await keyword when calling asynchronous functions that you need to WAIT for the data to be populated on.
Example main loop:
(async () => {
// put main loop code here
})();
You would then put your code inside that main loop BUT you have to make sure that you're adding the await keyword in front of every asynchronous call you use. in the original example provided, you'll have to declare your .then() functions that need to use await as async as well. This is confusing if you don't fully understand the asynchronous nature of javascript and I'll simplify it later, but I wanted to provide a working version of the original code provided so you can understand what changed:
(async () => {
var entriesLogged;
await fetch('url_to_the_file.json')
.then( async function(response){
return await response.json();
})
.then( function(data){
entriesLogged = data;
});
})();
The .then() call is typically used for post-processing so you can do work inside of the asynchronous call... but since in this example we're deliberately making it synchronous, we can avoid all of that and clean up the code by doing individual simple commands:
// Start main loop
(async () => {
let response = await fetch('url_to_the_file.json');
let entriesLogged = await response.json();
})();
That should get you what you're looking for and hopefully save anyone like us the heartache of spending hours of time trying track down something that there is a simple solution for.
Also want to give a huge call out to the place I got the solution for this:
https://www.sitepoint.com/delay-sleep-pause-wait/

Async await vs Promises vs Mapping?

How does one decide between promises, async awaits, and mapping operators like concatMap?
Here's my specific case, but I'm also curious about how you decide in general:
I am making an http call to my backend, and then I make another http call afterwards. When processing the json data from the second call, I need to use values that are returned by the first call. In this situation, is it better to use async await, a promise or concatMap? Also in general, what are the guidelines for deciding which to use?
Here is what I current have, using concatMap. (I am dynamically generating child components from my getTask http call, and each child component needs to have access to annotationFormats).
this.dashboardService.getAnnotationFormats()
.pipe(
concatMap(annotationFormats=> this.dashboardService.getTasks())
)
.subscribe(
(tasks)=>{
for(let task of tasks){
const componentFactory=this.CFR.resolveComponentFactory(DashboardItemComponent);
const componentRef=this.vc.createComponent(componentFactory);
componentRef.instance.task=task;
componentRef.instance.annotationFormats=annotationFormats;
componentRef.instance.compInteraction=this;
this.taskRef.push(componentRef);
}
}
);
Async/await and promises are basically the same with different syntax. Asynchronous code that will run once after some job has been finished.
As a rule, I would never never use none of those while using Angular. Angular comes with RxJS out of the box, which is so much more than promises. You can use RxJS for running async code once when a job has completed, but it also gives you the possibility of creating streams of data and manipulating them in so many different ways.
It does take a bit to fully understand RxJS and reactive programming but once you do you realize how much you can do with it.
In your case, I like to use the operator forkJoin, since the two requests seem independent from each other. You can give it a list of resources you want to obtain and will execute the async code in subscribe once they have all completed, which makes it perfect for http requests:
forkJoin({
annotationFormats: this.dashboardService.getAnnotationFormats(),
tasks: this.dashboardService.getTasks(),
})
.subscribe(
({tasks, annotationFormats})=>{
for(let task of tasks){
const componentFactory=this.CFR.resolveComponentFactory(DashboardItemComponent);
const componentRef=this.vc.createComponent(componentFactory);
componentRef.instance.task=task;
componentRef.instance.annotationFormats=annotationFormats;
componentRef.instance.compInteraction=this;
this.taskRef.push(componentRef);
}
}
);
Take your time to learn RxJS, I guarantee it will pay off. Whenever you are using RxJS and it feels too complex or wrong, that is because it probably is. Head to the RxJS documentation and look for something that might useful, and if you don't find anything a quick google search will probably get you the solution anyways. Point is, don't just use it blindly, always try to understand how it works.
I hope this is useful. :)
Edit:
For RxJS < 6.5, the syntax is a bit different:
forkJoin(
this.dashboardService.getTasks(),
this.dashboardService.getAnnotationFormats()
)
.subscribe(
([tasks, annotationFormats])=>{
for(let task of tasks){
const componentFactory=this.CFR.resolveComponentFactory(DashboardItemComponent);
const componentRef=this.vc.createComponent(componentFactory);
componentRef.instance.task=task;
componentRef.instance.annotationFormats=annotationFormats;
componentRef.instance.compInteraction=this;
this.taskRef.push(componentRef);
}
}
);
Notice we pass the resources as arguments, not as an object, and the result in the subscribe will be in an array form instead of an object too.
They have different use. async/await is used when you want to hold on a place where you have written some asynchronous code. while primises are tool to spot a place where async code is executed and invokes callback.

In React after calling setState, are there hard and fast rules for avoiding asynch errors when accessing/changing state?

I am learning React and this is my first time handling asynchronous operations. Currently I am building a simple calculator and have run into some minor issues after using setState and then immediately trying to access the state. It's been easy to work around the issues because my app is pretty simple. However, I haven't been able to find definitive rules for how long it takes after calling setState to be certain that state has actually been updated. Does it require a certain amount of time? Is it dependent on the structure of my code? Also, is it appropriate to use setTimeout after setState in order to give state time to update? If anyone could give me some insight into when React will update the state and save me the guesswork, it would be appreciated.
Does it require a certain amount of time?
No specific amount.
Is it dependent on the structure of my code?
In a general way, but that's not really the question. :-)
Also, is it appropriate to use setTimeout after setState in order to give state time to update?
No.
In React after calling setState, are there hard and fast rules for avoiding asynch errors when accessing/changing state?
Yes. They're covered in the documentation, particularly here. They are (at minimum):
The only way to know state has finished changing is to use componentDidUpdate or the completion callback you can provide to setState (the second argument):
this.setState(newState, () => {
// `this.state` has the updated state
});
If you're changing state based on existing state, you must use the form of setState that accepts a function as its first argument and calls it with the up-to-date state for you to change.
(Not async specific.) Never directly modify the state object or any object it refers to.
So for example: Suppose you have an array (items) and you need to push an entry into it if it's not already there:
// WRONG, breaks Rule #3
if (!this.state.items.includes(newItem)) {
this.state.items.push(newItem);
}
// WRONG, breaks Rule #2
if (!this.state.items.includes(newItem)) {
this.setState({items: [...items, newItem]});
}
// WRONG, breaks Rule #1
this.setState({items: [...items, newItem]});
doSomethingWith(this.state.items);
Instead:
this.setState(
({items}) => {
if (!items.includes(newItem)) {
return {items: [...items, newItem]};
}
},
() => {
doSomnethingWith(this.state.items);
}
);
But note that using the completion callback (the second function above) is usually an anti-pattern; really the only thing a component should do when state updates is re-render, and React will call render for you.
But this answer is not a substitute for reading through the documentation. :-)
setState takes a callback as second parameter:
this.setState({
foo: 'bar'
}, () => {
// This code will be executed after setState has been processed
});
Ideally, your components don't care about the timing of a setState update.
Most likely what you are looking for is the setState call that is based on the previous state...
this.setState((prevState) => {
return {
// I almost always just spread the state onto a new object.
...prevState,
// Then you'll want to add your updated properties below. This overrides the old state that you set above
someProp: newVal
};
});
From there, again, your components shouldn't care about when React actually performs the update! (As with all things there are exceptions of course)
If you want to access the state after setState() you can do that in the callback function for the same to verify the actual state is being updated.
Like this:
this.setSate ({
test : 'Hello Word'
}, () => {
console.log('this.state', this.state)
})
Whenever the state is changed, it compares the DOM and virtual DOM to find out differences, and renders them on the actual DOM.
https://reactjs.org/docs/state-and-lifecycle.html

Categories