React components must render synchronously. This is how React was designed; it cannot and will never change. Any way you hear of to render asynchronously is an abstraction that caches old render results and returns them synchronously. For this reason, you'll never be able to do what your code indicates.
However, I suspect that you don't actually care if the function is called before the data loads (which is what your code suggests), but rather that you want it called and to display a loading menu until the asynchronous operation completes and gives you its result. This is a common problem in React and is solved by having an initial empty state that you later set.
import React, { useState } from 'react';
function MyComponent() {
// Since you're using TypeScript, you can type this by
// setting the generic parameter
// e.g. useState<Product[] | null>(null);
const [products, setProducts] = useState(null);
listProducts().then(function(loadedProducts) {
setProducts(loadedProducts);
});
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>text: {products !== null && JSON.stringify(products)}</Text>
</View>
);
}
To make this more ES6:
import React, { useState } from 'react';
const MyComponent = () => {
const [products, setProducts] = useState(null);
listProducts().then(setProducts);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>text: {products && JSON.stringify(products)}</Text>
</View>
);
}
There's just one problem with this: every time MyComponent
is called, the data is loaded. You may want this in some circumstances, but for the vast majority of the time, you only want to reload the products when something changes. This is where the useEffect
hook comes in. It accepts a function that will only be called if the dependency list has changed.
useEffect(() => {
// This is called once on the first render and only
// called again whenever either dep1 or dep2 changes
// Note that a change in dep1 or dep2 does not necessarily
// mean your component will be called again (i.e. be
// rerendered). That only happens if dep1 or dep2 came
// from a useState hook, because to change a value from
// a useState hook, you must call the setter, which tells
// React to rerender.
console.log(dep1 + dep2);
}, [dep1, dep2]);
If you add the current minute to the dependency list, the product list will update at most every minute.
useEffect(() => {
listProducts().then(setProducts);
}, [
// Time in milliseconds rounded down to the minute
Math.floor(Date.now() / (60 * 1000))
]);
If you only want to call the function once, make the dependency list empty. If you want to call it after every render, don't specify a dependency list at all (i.e. useEffect(callback)
). Read more here.
A few other things: your App
class is pointless. You may be used to object-oriented programming from languages like Java and C#, but modern JavaScript avoids classes as much as is reasonable. Moreover, you don't need to extend React.Component
because you aren't ever rendering the class with React. I'd recommend moving the functions out of the classes. In addition, you seem unsure as to how Promises work. They are called asynchronously, the callback is called way after the enclosing function finishes unless you use async/await. I'll refactor this for you, but you really shouldn't be taking on something this difficult without the basics. Try this Promise guide for a start, then learn how async/await make it easy to avoid infinite .thens.
const getProducts = async () => {
const data = await listProducts();
// typeof isn't a function
console.log(typeof data, data);
}
const listProducts = async () => {
// Create the initDB() function the way I did this
const db = await initDB();
const favdrinks = await new Promise((resolve, reject) => {
db.transaction(async tx => {
const [tx, results] = await tx.executeSql(
'SELECT p.favorites FROM drinks p',
[]
);
const favdrinks = [];
console.log("Query completed");
var len = results.rows.length;
for (let i = 0; i < len; i++) {
let row = results.rows.item(i);
console.log(`Drinks favorited: ${row.favorites}`)
const { favorites } = row;
favdrinks.push({
favorites
});
}
console.log(favdrinks);
resolve(favdrinks);
})
});
// Refactor this function as well
await closeDatabase(db);
return favdrinks;
}
Putting it all together:
import React, { useState, useEffect } from 'react';
const listProducts = async () => {
const db = await initDB();
return new Promise((resolve, reject) => {
db.transaction(async tx => {
const [tx, results] = await tx.executeSql(
'SELECT p.favorites FROM drinks p',
[]
);
const favdrinks = [];
var len = results.rows.length;
for (let i = 0; i < len; i++) {
let row = results.rows.item(i);
const { favorites } = row;
favdrinks.push({
favorites
});
}
resolve(favdrinks);
})
});
await closeDatabase(db);
return favdrinks;
}
const MyComponent = () => {
const [products, setProducts] = useState(null);
useEffect(() => {
listProducts().then(setProducts);
}, []); // empty dependency list means never update
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>text: {products && JSON.stringify(products)}</Text>
</View>
);
}
EDIT: Seeing your comment on another answer, you aren't able to remove the class logic. I think you may be misunderstanding the requirements of whatever framework you are using, but if you're seriously unable to avoid it, you should create the object of the class you're using inside the useEffect
callback.