Async and await callout to firebase is not waiting - javascript

I have a dating-like app and I want to be able to query for new matches every time a user clicks on the "connection" tab button.
I am not sure if I am writing the await or async incorrectly but if the user moves too fast for the database to return the results, the returned matches are not loaded fast enough. What I have so far is: on the load of the page I callout to Firebase, when the user navigates away and then navigates back to the "connection" tab, I call back out to Firebase. the getMatches() method is the callout to firebase.
const MatchesScreen = ({navigation}) => {
const {state, updateDislikedQueue, updateLikedQueue, getMatches} = useContext(AuthContext);
const [loaded, setLoaded] = useState(false);
const [queue, setQueue] = useState({});
const [noMatches, setNoMatches] = useState(false);
const [updateProfileAndPreferences,setUpdateProfileAndPreferences] = useState(false);
const getMatchesMethod = async () => {
getMatches().then(matches => {
if (!matches) {
Alert.alert("Update Preferences and Profile before connecting");
setUpdateProfileAndPreferences(true);
} else {
setUpdateProfileAndPreferences(false);
let cardData = [];
for (m in matches) {
if (matches[m].id == state.id) {
continue;
} else {
let user = {
id: matches[m].id,
fullName: matches[m].info.fullName
};
cardData.push(user);
}
}
if (cardData.length > 0) {
setQueue(cardData);
setLoaded(true);
} else {
setNoMatches(true);
Alert.alert("No Connections Available");
}
}
});
};
useEffect(() => {
getMatchesMethod();
const unsubcribe = navigation.addListener("willFocus", () => {
getMatchesMethod();
});
// return unsubcribe.remove();
}, []);
Also when I try to unsubscribe, the listener doesn't appear to work when the user navigates back and forth. Any help on what I am doing wrong with the async calls and the listener would be greatly appreciated.

I think you just forgot the await keyword in your function
Happens to me all the time ahaha

I recommend using the await keyword in front of getMatches() instead of the .then() syntax. It makes the code read more synchronously, and it can help prevent errors. Await must always be called from an asynchronous function. It cannot be called from a non async function.
I think you will also need to use an await in front of getMatchesMethod();
Checkout this article for help with calling asynchronous code inside an useEffect()
https://medium.com/javascript-in-plain-english/how-to-use-async-function-in-react-hook-useeffect-typescript-js-6204a788a435
Ex:
const unsubcribe = navigation.addListener("willFocus", async () => {
await getMatchesMethod();
});

I found the issue. I was on V3 of react-navigation which is severely outdated was was conflicting with my react-native and expo versions.
I am in the process of updating everything (RN > .60, EXPO sdk > 35, #react-navigation V5) and will be able to use the listeners for #react-navigation v5 on my getMatches() method.

Related

Why does my react function function iterate twice?

I am currently trying out a project with the PokeAPI. And have used his guide for help. I can't get rid of the problem that the function iterates twice when called in the useEffect.
When I run the following code with the getAllPokemons in the useEffect
const PokeListPage = () => {
const [layoutToggle, setLayoutToggle] = useState(false);
const [allPokemons, setAllPokemons] = useState([]);
const [loadPoke, setLoadPoke] = useState(
"https://pokeapi.co/api/v2/pokemon?limit=20"
);
useEffect(() => {
getAllPokemons();
console.log(allPokemons);
}, []);
const getAllPokemons = async () => {
const res = await fetch(loadPoke);
const data = await res.json();
setLoadPoke(data.next);
function createPokemonObject(result) {
result.forEach(async (pokemon) => {
const res = await fetch(
`https://pokeapi.co/api/v2/pokemon/${pokemon.name}`
);
const data = await res.json();
setAllPokemons((currentList) => [...currentList, data]);
});
}
createPokemonObject(data.results);
console.log(allPokemons);
};
I get doublets of the first 20 objects in allPokemons. See output:
enter image description here
But when I remove the function and uses a button to trigger the function it behaves as expected. Which means that the function populates the allPokemon array with one object per pokemon. See output.
enter image description here
I have tried everything from copying entire files from other repositories and with an accuracy I didn't knew I had followed different tutorials but the problem remains. Does anyone know why?
It's bcz, you are rendering your app into a React. Strict mode component that runs specific functions and methods twice as a way to help you detect unintentional side effects. Since the side-effect is a state update, this triggers a rerender.
Use a useEffect to run the effect once when the component mounts.

React app not re-rendering after infinite scroll implementation

Hi guys hoping someone can help me with an issue. I built a function that fetches user posts from backend and returns them as a response:
const [posts, setPosts] = useState([]);
const newsFeed = async () => {
await axios
.post(`${process.env.REACT_APP_API}/news-feed`)
.then((res) => {
setPosts(res.data);
})
.catch((err) => {
console.log(err);
});
};
This function gets called by useEffect and also whenever a post is submitted, liked, commented on etc. so that users' posts are always re-rendered every time they are modified or added.
It was working fine until I decided to implement Infinite Scroll to my application. I installed the npm package react-infinite-scroll-component and modified my function to look like this:
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const newsFeed = async () => {
await axios
.post(
`${process.env.REACT_APP_API}/news-feed/${page}`)
.then((res) => {
let newPosts = posts;
newPosts = newPosts.concat(res.data);
setPosts(newPosts);
setPage(page + 1);
})
.catch((err) => {
console.log(err);
});
};
The infinite scroll is working just fine but now the posts are not being re-rendered every time this function gets called and instead I need to refresh the page to see changes. I tried resetting the state of page back to 1 again on my postSubmit/likeHandler functions but this didn't have any effect. I'm not seeing any errors in the console so am unsure what is going on.
Replace let newPosts = posts; with let newPosts = [...posts]; Passing the same array reference to setState will not cause an update, since the value hasn't changed. By using [...posts], you are creating a new array, causing the component to update.

Not getting data from firebase on opening the app

I am trying to get data from firebase but it returns empty value when the app loads, but if I edit something on that file even the commented line, then the data loads and app runs, I want when the app opens all data should be there from firebase to run app. and also how to arrange "grabbedData" in reverse order tried grabbedData.reverse() but doent work.
const Getdata = async () => {
let grabbedData = [];
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
// console.log("snapshot....", snapshot);
grabbedData.push(snapshot.val());
});
setUserdata(grabbedData);
console.log("grabbedData", grabbedData); // empty value here :(
if (grabbedData) {
let getfranchiseuid = "";
Object.keys(grabbedData).map(function (key) {
let y = grabbedData[key];
Object.keys(y).map(function (key2) {
let x = y[key2];
if (key2 === uid) {
getfranchiseuid = x.franchiseuid;
}
});
});
if (getfranchiseuid) {
let customerList1 = [];
firebase
.database()
.ref(`/serviceProvider/${getfranchiseuid}/franchise/customers`)
.orderByKey()
.on("value", (snapshot) => {
customerList1.push(snapshot.val());
});
setCustomerList(customerList1);
console.log("customerList1customerList1", customerList1);
}
}
};
useEffect(() => {
var unsubscribe = firebase.auth().onAuthStateChanged(function (user) {
if (user) {
storeUser({ user });
setUser(user);
setEmail(user.email);
setUid(user.uid);
} else {
// No user is signed in.
}
});
unsubscribe();
Getdata();
}, []);
Data is loaded from Firebase asynchronously. Since this may take some time, your main JavaScript code will continue to run, so that the user can continue to use the app while the data is loading. Then when the data is available, your callback is invoked with that data.
What this means in your code is that (for example) right now your setUserdata is called before the grabbedData.push(snapshot.val()) has run, so you're setting any empty user data. You can most easily see this by setting some breakpoints on the code and running it in a debugger, or by adding logging and checking the order of its output.
console.log("1");
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
console.log("2");
});
console.log("3");
When you run this code, the output will be:
1
3
2
This is probably not what you expected, but it is exactly correct and does explain your problems.
The solution for this is always the same: any code that needs the data from the database must be inside the callback, or be called from there.
So for example:
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
grabbedData.push(snapshot.val());
setUserdata(grabbedData);
});
this will ensure that setUserdata is called whenever you updated the grabbedData.
Since you have much more code that depends on grabbedData, that will also have to be inside the callback. So the entire if (grabbedData) { block will need to be moved, and probably others. If you keep applying the solution above, the code will start working.
This is a very common problem for developers that are new to calling asynchronous cloud APIs, so I highly recommend reading some of these other answers:
Why Does Firebase Lose Reference outside the once() Function?
Best way to retrieve Firebase data and return it, or an alternative way
How do I return the response from an asynchronous call? (this one is not specific to Firebase, as the problem is not specific to Firebase)

How to call function after URLSearchParam?

I need redirect page after it load and get param from URL. I can do by button click. But how I can make page redirect automatic after page have get param from URL (without user input)?
Thanks!
const handleClick = async (event) => {
const stripe = await stripePromise;
const { error } = await stripe.redirectToCheckout({
param,
});
}
const param = new URLSearchParams(window.location.search).get('param');
const Page = () => {
React.useEffect(() => {
window.scrollTo(0, 0);
}, []);
return (
<section >
<button role="link" onClick={handleClick}>
Press
</button>
</section>
);
};
export default Page;
You could probably just use your useEffect hook there to redirect the page, instead of using the onClick handler. e.g.
const handleLoad = async () => {
const stripe = await stripePromise;
const { error } = await stripe.redirectToCheckout({
param,
});
}
const param = new URLSearchParams(window.location.search).get('param');
const Page = () => {
React.useEffect(() => {
handleLoad();
}, []);
return null;
};
export default Page;
Though, you don't really even need to load up React for any of this. You could just make this page a static html page with a script tag that does the redirect logic, or something similar to that.
After further discussion, we found out that in order for this stripe stuff to work on Safari-mobile, we needed to wait for the page's load event before calling handleLoad(), so something like this:
const handleLoad = async () => {
const stripe = await stripePromise;
const { error } = await stripe.redirectToCheckout({
param,
});
}
const param = new URLSearchParams(window.location.search).get('param');
window.addEventListener('load', () => handleLoad())
You're going to want to parse those query parameters in the init/load handler. You can see an example of this here. Note that the event listener on DOMContentLoaded kicks everything off. An example of this in action can be seen here.
I noticed that your code uses param in the Checkout redirect directly. If param is a checkout session that you should be generating these on demand, not sending them in emailed links etc, as they are short-lived. In the example above, the code param is read and use to generate a new checkout session via a fetch and then that is used for the redirect.

Unnecessary parameter in useEffect dependency array

I'm creating an application where users can create and share notes.
To share each other's notes users have to send requests to specific users.
The requests are fetched whenever home is loaded.
However, requests is a context since it is also consumed in the toolbar and requests page to show the presence of the requests
When I'm using setRequsts method of the context to set all the requests after home loads, the fetch goes into an infinite loop of /noteand /me URLs, since the setRequests method is also provided in the dependency array of useEffect
When removed, useEffect show missing dependencies. What's the work around?
const {setRequests } = useContext(RequestsContext)
const [notes, setNotes] = useState([])
const [fetched, setFetched] = useState('')
const { isAuthenticated } = props
const {page}=useContext(PageContext)
const [sortBy,setSortBy]=useState('latest')
useEffect(() => {
const fetch = async () => {
try {
let url = 'http://192.168.56.1:5000/api/v1/note', p, sort
if (page) p = `?page=${page}&limit=12`
if (sortBy === 'latest') {
sort=''
} else if (sortBy === 'most_liked') {
sort='&sort=likes'
}
const res = await Axios.get(url+p+sort)
setNotes(res.data.data)
if (res.data.data.length > 0) {
setFetched('Y')
} else {
setFetched('N')
}
} catch (err) {
console.log(err)
} finally {
if (isAuthenticated) {
const fetch = async () => {
const res = await axios.get(`user/me`)
if (res.data.data.createdPosts.length > 0) {
const arr = res.data.data.createdPosts.map(el => el.request)
console.log(arr)
setRequests(arr)
}
}
fetch()
}
}
}
fetch()
}, [isAuthenticated, /* setRequests, */ page, sortBy])
The problem is that the context provides a technically different setRequests function on each render (that have a different address). This causes useEffect to fire on each render.
To work around this, you could wrap setRequests in a useCallback() hook, like so:
// ...
const wrappedSetRequests = useCallback(setRequests, []);
// ...
useEffect(() => {
// do your stuff using 'wrappedSetRequests' instead of setRequests.
}, [ wrappedSetRequests /*...*/ ]);

Categories

Resources