React Query Update Cached Data By Index Key - javascript

How do I update existing records by their index key?
Im not so familiar with React Query.
When a button is clicked, then this will trigger onClickHandler to update the object value by its index key.
import {useQuery, useQueryClient} from '#tanstack/react-query';
const {
data: comments,
isError,
isLoading
} = useQuery({
queryKey: ['comments'],
queryFn: async () => {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/1/comments`);
return response.json();
}
});
const onClickHandler = (index) => {
const previousData = queryClient.getQueriesData(['comments']);
queryClient.setQueryData(['comments'], (comments) => {
comments.map((r, i) => {
r['is_shown'] = false;
if(i === index) {
r['is_shown'] = true;
}
return r;
});
});
};

you forgot to return the new data inside setQueryData function.

Related

How to catch Firebase promise in React?

I have a simple function that checks if the user has Premium access or not:
export const checkPremium = async () =>{
if (auth.currentUser) {
const q = query(collection(db_firestore, 'users'));
onSnapshot(q, (querySnapshot) => {
querySnapshot.forEach((doc) => {
if (doc.id === auth.currentUser.uid) {
return doc.data().userSettings.hasPremium
}
});
})
}
else{
return false
}
}
I tried to catch this in various ways, but no luck, it always returns an "undefined" object.
const getPremium = async => {
checkPremium.then((response) => console.log(response))
}
const getPremium = async => {
let hasPremium = await checkPremium()
}
let hasPremium = checkPremium()
What is the correct way to get the returned Boolean value?
onSnapshot is meant for listening to a collection continuously, getting repeatedly notified as its value changes. It does not create a promise, so the promise returned by getPremium is unrelated to the data you will eventually get in onSnapshot. If you just want to get the value once, you should use getDocs:
export const checkPremium = async () =>{
if (auth.currentUser) {
const q = query(collection(db_firestore, 'users'));
const querySnapshot = await getDocs(q);
const match = querySnapshot.docs.find(doc => doc.id === auth.currentUser.uid);
if (match) {
return doc.data().userSettings.hasPremium);
} else {
return false;
}
}
else{
return false
}
}
Also, instead of getting all the users and then using client side code to find the one with the right id, you could just fetch that individual doc directly:
const ref = doc(db_firestore, 'users', auth.currentUser.uid)
const snapshot = await getDoc(ref);
const data = snapshot.data();
if (data) {
return data.userSettings.hasPremium
} else {
return false
}

Return value firestore onSnapshot in react

i have a onSnapshot query in a function:
//firebaseutil.js
export async function getShorts(uid) {
const q = query(collection(db, 'shorted'), where('owner', '==', uid));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const urls = [];
querySnapshot.forEach((doc) => {
urls.push({
url: doc.data().url,
shorturl: doc.data().shorturl,
hits: doc.data().hits,
});
});
console.log(urls);
return urls;
});
}
Which correctly logs the data, and relog it if i change it on the firestore collection (as expected)
i am trying to access these data from a user dashboard this way:
//dashboard.js
import { getShorts } from '../lib/fbutils';
import { useEffect, useState } from 'react';
export default function Dashboard() {
const [myurls, setUrls] = useState([]);
useEffect(() => {
const fetchShorts = async () => {
if (user) {
const urls = await getShorts(user.uid);
setUrls(urls);
console.log(myurls);
console.log(urls);
}
};
fetchShorts();
}, []);
user.id is correctly set, but both urls and myurls are logged as undefined (i was thinking at least for a Promise pending)
what am i doing wrong? i usually use this pattern to retrieve data, but it's my first time i get data from a firestore subscription
The onSnapshot() does not return a promise and your getShorts() function returns before the data is received. You can return a promise from that function as shown below:
let fetched = false;
export function getShorts(uid) {
return new Promise((resolve, reject) => {
const q = query(collection(db, 'shorted'), where('owner', '==', uid));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const urls = querySnapstho.docs.map((d) => ({ id: d.id, ...d.data() }))
if (!fetched) {
// URLs fetched for first time, return value
fetched = true;
resolve(urls);
} else {
// URLs fetched already, an update received.
// TODO: Update in state directly
}
})
})
}
This should return all the URLs when you call the function await getShorts(user.uid); but for the updates received later, you'll have to update them in the state directly because the promise has resolved now.

How to wait async data to start sync function

I get some data from an api call and set them in a state. Then I use this state variable in another function to filter some data. When the user opens the interface for the first time the data doesnt show because the sync function gets the empty data from the state.
Here is the code :
const [evQuestion, setEvQuestion] = useState();
const [answers, setAnswers] = useState();
const getEvaluationsQuestionsByOrganizations = async (evalId) => {
const response = await apiStandarts.get(`/evaluation-questions?organization_evaluation=${evalId}`);
setEvQuestion(response.data);
};
const evAnswers = () => {
const evAnswers = questions.map(q => {
return evQuestion?.map(ev => {
return q.question_options.find(i => i.id === ev.questOptionId)
});
});
const filterAnswers = evAnswers.map(q => {
return q?.filter(Boolean)
})
const answersToObject = filterAnswers.map(item => {
return convertArrayToObject(item)
});
const arr = {...answersToObject}
const obj2 = Object.fromEntries(
Object.entries(arr).map(([key, value]) => [key, value])
)
const obj3= Object.values(obj2).map(item => {
return {[item.question]: {...item}}
})
const savedAnswers = convertArrayToObject(obj3);
console.log(savedAnswers)
setAnswers(savedAnswers)
}
useEffect(() => {
getEvaluationsQuestionsByOrganizations();
evAnswers();
}, [])
I've tried to wrap the evAnswers function in a settimeout function but with no luck. How can I achieve this, any ideas?
Try adding another useEffect hook that depends on evQuestion state.
useEffect(() => {
getEvaluationsQuestionsByOrganizations();
}, []);
useEffect(() => {
evAnswers();
}, [evQuestion]);
the function getEvaluationsQuestionsByOrganizations(..) is defined as async function, but you are using it synchronously, in that case you should call your codes as below:
useEffect(() => {
const fetchedDataAPI = async () => {
return await getEvaluationsQuestionsByOrganizations();
};
fetchedDataAPI
.then(res => { evAnswers();})
.catch(err => {..});
;
}, []);

How do I use async/await with Array.filter properly in React?

I'm creating just a simple currency converter (React + Typescript). Here is my component code:
const App = () => {
const [countries, setCountries] = useState<Array<CountriesProps>>([])
const [currencies, setCurrencies] = useState<Currencies>({})
const filteredCountries = async () => {
const { data } = await axios.get('https://restcountries.eu/rest/v2/all')
const answer: Array<CountriesProps> = data
const filtered = answer.filter(country => {
for (let i in currencies) {
if(i === country.currencies[0].code) {
return country
}
}
})
setCountries(filtered)
}
useEffect(() => {
axios
.get('https://api.frankfurter.app/currencies')
.then(res => {
setCurrencies(res.data)
})
.catch(err => {
console.log(err)
})
}, [])
useEffect(() => {
filteredCountries()
}, [])
return (
...
)
}
export default App
I come across the problem, during launching the app. After getting currencies information from the server I need to fetch countries information. After getting countries I need to filter them and put them in my state (countries) and send it to another component and so on. But during launch of the app filter function doesn't work and I got no filtered countries and so I don't have any info in my state. I think that filter function needs to be an asynchronous, so we need to wait before setting our state through setCountries function. How to do it properly in my case or I did all the logic wrong?
As long as requested countries rely on fetched currencies and you don't seem to be using one without the another, you may stack .get()-requests accordingly or use respective async...await alternative:
fetchData = async () => {
const currenciesResponse = await axios.get(currenciesEndpoint),
currenciesData = await currenciesResponse.data,
countriesResponse = await axios.get(countriesEndpoint),
countriesData = await countriesResponse.data,
filteredCountriesData = countriesData.filter(_country => {
const {
currencies: [{ code }]
} = _country;
return currenciesData[code];
});
setCurrencies(currenciesData);
setCountries(filteredCountriesData);
}
useEffect(() => {
fetchData();
}, [])
Following is a full-blown demo as a proof-of-a-concept
See if this helps.
const [countries, setCountries] = useState<Array<CountriesProps>>([])
const [currencies, setCurrencies] = useState<Currencies>({})
const filteredCountries = async () => {
const { data } = await axios.get('https://restcountries.eu/rest/v2/all')
const answer: Array<CountriesProps> = data
const filtered = answer.filter(country => {
return currencies[country.currencies[0].code]
})
setCountries(filtered)
}
useEffect(() => {
axios
.get('https://api.frankfurter.app/currencies')
.then(res => {
setCurrencies(res.data)
})
.catch(err => {
console.log(err)
})
}, [])
useEffect(() => {
filteredCountries()
}, [currencies])
try using this:
const App = () => {
const [countries, setCountries] = useState<Array<CountriesProps>>([])
const [currencies, setCurrencies] = useState<Currencies>({})
const filteredCountries = async () => {
const res = await axios.get('https://api.frankfurter.app/currencies')
// you don't need a state for currencies but in case you find a use case for it,
// you're just setting the currencies here for future use cases.
setCurrencies(res.data);
const { data } = await axios.get('https://restcountries.eu/rest/v2/all')
const answer: Array<CountriesProps> = data
const filtered = answer.filter(country => {
for (let i in res.data) {
if(i === country.currencies[0].code) {
return country
}
}
})
setCountries(filtered)
}
useEffect(() => {
filteredCountries()
}, [])
return (
...
)
}
export default App

Promise inside a loop inside an async function

I am working on a project using react and firebase and redux and I have some items that did created by a user. I'm storing the id of the user in the item object so i can populate the user later when i get the item to display.
Now I'm trying to get the items and modify them by replacing the user id with the actual info about the user but I have a promises problem. In my code I just get an empty array which mean the modification didn't get resolved before I return the final result.
export const getItems = () => {
return (dispatch, getState, { getFirebase }) => {
const firestore = getFirebase().firestore();
const items = [];
const dbRef = firestore.collection('items').orderBy('createdAt', 'desc').limit(2);
return dbRef
.get()
.then((res) => {
const firstVisible = res.docs[0];
const lastVisible = res.docs[res.docs.length - 1];
async function getData(res) {
/////////////////////////////////////////////// how to finish this code befor jumping to the return line
await res.forEach((doc) => {
firestore
.collection('users')
.doc(doc.data().owner)
.get()
.then((res) => {
items.push({ ...doc.data(), owner: res.data() });
});
});
////////////////////////////////////////////////
return { docs: items, lastVisible, firstVisible };
}
return getData(res);
})
.catch((err) => {
console.log(err);
});
};
};
I don't get exactly what you are trying to do, but I would suggest putting some order to make your code easy to read and work with.
You can use for of to manage async looping. I suggest something like this, disclaimer, I did it at the eye, problably there are some errors, but you can get the idea.
const getAllDocs = function (data) {
let temp = [];
data.forEach(function (doc) {
temp.push(doc.data());
});
return { data: temp };
};
const getDoc = snap => (snap.exists ? { data: snap.data() } : {});
export const getItems = () => {
return async (dispatch, getState, { getFirebase }) => {
const firestore = getFirebase().firestore();
const dbRef = firestore.collection('items').orderBy('createdAt', 'desc').limit(2);
const usersRef = firestore.collection('users');
let temps = [];
const { data: items } = await dbRef.get().then(getAllDocs);
const firstVisible = items[0];
const lastVisible = items[items.length - 1];
for (const item of items) {
const { data: user } = await usersRef.doc(item.owner).get().then(getDoc);
const owner = {
/* whatever this means*/
};
temps.push({ ...user, owner });
}
return { docs: temps, lastVisible, firstVisible };
};
};
The problem is that an array of Promises is not itself a Promise -- so awaiting it will be a no-op.
You can solve this using Promise.all if you want to load them all asynchronously.
const items = await Promise.all(res.map(async (doc) => {
const res = await firestore.collection('users').doc(doc.data().owner).get();
return { ...doc.data(), owner: res.data() };
});
Otherwise you can await in a for loop as suggested in other answers.

Categories

Resources