How to turn a function into an ASYNC AWAIT - reactjs

Currently, I'm passing a value from a Child Component to a Parent in React.
const ParentComponent = () => {
const [callback, setCallback] = useState(null);
const handleCallback = (childData) => {
console.log(childData);
return childData;
};
let childDataType = handleCallback();
if (childDataType === "success") {
setCallback(true)
} else if (childDataType === "error") {
setCallback(true)
}
return (
<div dataValue={dataValue(callback)}/>
)
}
const ChildComponent = ({dataValue}) => {
let callback = thisData[index].value
return (
<div dataValue={dataValue(callback)}/>
)
}
I'm trying to use that value to set a state. If the value from the child equals a string then the state is true else false.
Right now I'm able to get the data to console.log(childData) inside handleCallback. However, the data comes back undefined at first then sets to a value. Because of this, it sets childDataType to undefined. Which in turn sets my state to undefined.
I need to have the variable childDataType to wait for the function to run and return a defined value before trying to set the callback's state. How do I get handleCallback to await a defined value before returning its initial value?

What I like to do is the following
const handleCallback = (childData) => {
return new Promise((resolve,reject)=>{
resolve(childData)
})
};
then u can do
let childDataType = await handleCallback();
This is a nice approach because if any error happens in the function u just call reject(error).

You can change the function to an async function and await for it to return the value.
const handleCallback = async (childData) => {
console.log(childData);
return childData;
};
let childDataType = await handleCallback();

Did you try to use the "await" expression?
let childDataType = await handleCallback();
Check this for more info:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await

Related

How to display an array in JSX from a function in React?

I implemented a function where I fetch all Docs from a Firebase collection on a click.
Now I want to display each doc I fetched in a <div> container in JSX. When I try to take the array and display it, I´m getting the error that the array is not found.
This is my code:
async function getAllDivs(){
const querySnapshot = await getDocs(collection(db, "Div"))
const allDivs = [];
querySnapshot.forEach(doc => {
allDivs.push(doc.data().DivContent);
});
}
You would have to return the array from the function, because of the "scope".
Example:
//your current function
async function getAllDivs(){
const querySnapshot = await getDocs(collection(db, "Div"));
return querySnapshot.map((doc) => doc.data().DivContent);
}
//your component
let divs = getAllDivs(); //you can now use "divs" in this scope
return (
<>
divs.map((current_div) => { <div>{current_div}</div> })
</>
)
Also, I suggest against pushing data to an array labeled as const, as it could be confusing for someone else reading your code.
I think you could use something like this:
const MyComponent = () => {
const [docs, setDocs] = useState();
const onClickHandler = async () => {
const docs = await getDocs(collection(db, "Div"));
setDocs(docs);
}
return (
<>
<button onClick={onClickHandler}>Get docs</button>
{docs && docs.map(doc => (
<div>{doc.data().DivContent}</div>
))}
</>
)
}
If DivContent contains HTML you can use dangerouslySetInnerHTML.

Value not defined in first render but defined in next render

I recently started using useEffect hook, and I'm facing some issues. This is a simple App component that renders an input field and submit button. On hitting submit selectItem is called which in turn calls an async function getNames(). getNames function checks if there is an existing entry, if so it returns, otherwise it calls 3rd party API getNewNames() to get newNames. I tried setting the state with this newNames field, but it seems like it is undefined in first render. But after the first render it is defined. How do I make sure that I have newNames field, so that it doesn't return undefined in any renders?
const App = () => {
const [namesArr, setNamesArr] = useState([])
const [name, setName] = useState('')
useEffect (()=> {
console.log('Inside use Effect')
}, [namesArr])
const changeInput = (val) => {
setName(val)
}
const selectItem = async() => {
const returnedVal = await getNames()
// ReturnVal is empty in first render, but filled in second render.
/
}
const getNames = async() =>{
const existingNames = namesArr.find((name)=> name === name)
if(existingNames){
return 'We have an entry'
}
else{
console.log(`Names are not reloaded properly, need to re-render`)
const newNames = await getNewNames() // this is
setName((oldNames)=> [...oldNames, newNames])
return namesArr
}
}
return <div>
<input value={name} onChange={(e)=> changeInput(e.target.value)}></input>
<button onClick={()=> selectItem()}></button>
</div>
}
I think your problem is related to the setName method. In the line after setName console.log(namesArr) will be undefined. So how can we fix this?
const getNames = async() =>{
const existingNames = namesArr.find((name)=> name === name)
if(existingNames){
return 'We have an entry'
}
else{
const newNames = await getNewNames();
// We created our new list outside
// If x is a list, prepend an ellipsis ...newNames
const newList = [...namesArr, newNames];
// setName string is a state. Incorrect state updating.
// setNamesArr instead of setName
setNamesArr(newList)
return newList;
}
}
Now there are two possibilities here.
returnedVal === array or returnedVal === 'We have an entry'
const selectItem = async() => {
const returnedVal = await getNames();
console.log(returnedVal); // array or string.
}

Get value and not [object Promise] from async function in React Component

I have a function which is async and returns a value.
How do I get this value when I call that function within a React function component?
For me it always shows [object Promise].
I already tried some stuff with the Effect Hook.
function Home() {
const { publicKey } = useWallet();
const [buttonClicked, setClick] = useState(false);
let tokenAccounts;
useEffect(() => {
if (buttonClicked) {
//This is the async function that I am calling
tokenAccounts = getTokenAccounts(publicKey);
console.log("token account: " + tokenAccounts);
readyToShow = true;
}
})
function handleButClick() {
setClick(true);
}
let display;
if (buttonClicked) {
display = <div>test{tokenAccounts}</div>
} else {
display = (publicKey && <button onClick={handleButClick}>click</button>);
}
return (
<div>
{display}
</div>
);
}
(It shows button and after the button is clicked it does show test and should also show the variable tokeAccounts which in my case is a [object Promise])
Putting an await in front of the function call would be my intentional solution but then it says Unexpected reserved word 'await'
Try this:
const Home = () => {
const { publicKey } = useWallet();
const [tokenAccounts, setTokenAccounts] = useState(undefined);
const handleButClick = () => {
getTokenAccounts(publicKey)
.then(result => setTokenAccounts(result));
// Depending on API may need result.data or other
}
return (
<React.Fragment>
{tokenAccounts === undefined && publicKey &&
<button onClick={handleButClick}>click</button>
}
{tokenAccounts && <div>test{tokenAccounts}</div>}
</React.Fragment>
);
}
When the component first loads the tokenAccounts state variable is undefined so your button element will be displayed. When the button is clicked the event handler calls your async action and accesses the return value via the .then statement (also check out .catch and .finally). The tokenAccounts state variable is then set and the state change causes the component to refresh. Since tokenAccounts now has a value it is displayed and the button element hidden.
function Home() {
const { publicKey } = useWallet();
const [buttonClicked, setClick] = useState(false);
let tokenAccounts;
useEffect(() => {
const getTokenAccountsLocally = async () => {
if (buttonClicked) {
tokenAccounts = await getTokenAccounts(publicKey);
console.log("token account: " + tokenAccounts);
}
}
getTokenAccountsLocally();
})
function handleButClick() {
setClick(true);
}
let display;
if (buttonClicked) {
//runs through this before tokenAccounts is loaded with the value
display = <div>this is the {tokenAccounts}</div>
} else {
display = (publicKey && <button onClick={handleButClick}>click</button>);
}
return (
<div>
{display}
</div>
);
}
Wrapping the async function call around another async function locally and then call it so I can add wait for the other async function call kind of works. tokenAccounts is now loaded with the returned value from the async function call. BUT it gets shown before it actually loaded the value.

Data from useEffect not being rendered

I have a function component:
// MovieOverview.tsx
const MovieOverview = () => {
const {data} = useQuery(resolvers.queries.ReturnAllMovies);
const movie: IMovie = useReactiveVar(moviesVar);
let movies: IMovie[] = data?.movies;
let movieRows;
const setMoviesInRow = (movies: IMovie[]) => {
const numberOfMovies = Math.floor((window.innerWidth -24) / 185);
return chunk(movies, numberOfMovies);
};
useEffect(() => {
movie ? movies = movies?.concat(movie) : null;
movies ? movieRows = setMoviesInRow(movies) : null;
movies ? console.log(movieRows) : null;
}, [movies, movie]);
return (
<div>
{movieRows?.length}
</div>
);
};
The console.log(movieRows) shows what I expect, an array with some objects. But in the template movieRows is undefined.
Setting a variable (movieRows = setMoviesInRow(movies)) doesn't cause a render, which is why you don't see it. In order to cause a render using React Hooks you must use a "set" function that is returned from the hook (like what is returned from useState). This function updates the variable and triggers a render.
For your purpose, useQuery doesn't return a "set" function (since you shouldn't need to change the return values of a query). What you should do is use window.innerWidth as a state variable that will update with its own useEffect - something like this example. You might be able to use an existing package like this so you don't have to write the hook yourself.
Then you can use it in your component without another useEffect:
// MovieOverview.tsx
const MovieOverview = () => {
const windowInnerWidth: Number = useWindowInnerWidth();
const {data} = useQuery(resolvers.queries.ReturnAllMovies);
const movie: IMovie = useReactiveVar(moviesVar);
let movies: IMovie[] = data?.movies;
let movieRows;
movie ? movies = movies?.concat(movie) : null;
const numberOfMovies = Math.floor((windowInnerWidth -24) / 185);
movies ? movieRows = chunk(movies, numberOfMovies) : null;
return (
<div>
{movieRows?.length}
</div>
);
};
The component will be re-rendered once when the window is ready and again when the query results arrive.

Objects are not valid as a React child, recived data in console but not on display

I received dataAddress in the console with actual coordinates x, y. But I have a problem Objects are not valid as a React child (found: [object Promise]). I really don't understand what is wrong.
const BlogPage = () => {
const data = useStaticQuery(graphql`
query{
allContentfulBlogPost{
edges{
node{
location{lat, lon}
}
}
}
}
`)
const token = 'token';
let getAddress = async (x, y) => {
const api_url = await fetch(`https://api.mapbox.com/geocoding/v5/mapbox.places/${x},${y}.json?access_token=${token}`)
const dataAddress = await api_url.json()
console.log(dataAddress);
}
return (
<Layout>
{
data.allContentfulBlogPost.edges.map((edge) => {
return (
<p>
{
getAddress(edge.node.location.lat, edge.node.location.lon)
}
</p>
)
})
}
</Layout>
)
}
A number of things are wrong in your code. Firstly, you should put the API call in a useEffect hook so it doesn't get called upon every render. Secondly, the function getAddress() returns a Promise object as it is marked as async. So you can't pass that in as a React child without at least adding the await keyword. Also you should have getAddress() function return the data of interest (probably the value of dataAddress) so you can use that value outside the function (right now, the function doesn't return anything). Or even better, set that value to your React state.
You could try:
...
const [address, setAddress] = React.useState({});
React.useEffect(() => {
getAddress(edge.node.location.lat, edge.node.location.lon);
}, [])
...
And in the getAddress() function, set your returned value as part of your state:
let getAddress = async (x, y) => {
const api_url = await fetch(`https://api.mapbox.com/geocoding/v5/mapbox.places/${x},${y}.json?access_token=${token}`)
const dataAddress = await api_url.json()
console.log(dataAddress);
setAddress(dataAddress);
}
And in your JSX, just use the state value
...
<Layout>
{data.allContentfulBlogPost.edges.map((edge) => {
return (
<p>{address}</p>
)
})}
</Layout>
...
Of course, this assumes the value of address isn't an object. If it is you'd have to use only primitive properties of that object in your JSX. For example, if it's an object with properties: x and y, your JSX could look like
...
<p>({address.x}, {address.y})</p>
...

Resources