I need to invalidate some queries in the onSuccess of an useMutation.
I have a queryKey schema like this:
.....
1-["questions", "by_vendor" , "{vendor_code}" , "by_status", "{status}"]
2-["questions", "by_status", "{status}"]
3-["questions", "by_vendor", "{vendor_code}"]
.....
I'm using useQueryClient to get the query client.
I need to invalidate the querykeys where the "by_status" is used.
Is there a way of accessing active queryKeys.
There are methods like queryClient.getQueryData() but all require the queryKey in advance.
Thank you!
I would suggest re-organizing your query keys slightly. It is much easier to use objects for keys than using the suggested 'list' style. e.g.
return useQuery(
['questions', { by_vendor: vendor, by_status: status }],
async ({queryKey}) => {
// Stuff to use the query key and get api data
}
);
Then you can invalidate them later with:
queryClient.invalidateQueries({
queryKey: ['questions', { by_status: status }],
})
For more information check out Effective React-Query Keys and Query Invalidation
As mentioned by Chad S. invalidating queries can be done by using objects as the keys, the method will work by a match, it does not necessarily need the exact query to match, and there is a flag for that: exact: true.
But to get the querykeys, if that is what you are looking for, then here's an explanation.
To access the queryKeys that the client is listening to, use the useQueryClient hook.
const queryClient = useQueryClient()
With the hook called, have access to the method: getQueryCache().
This will return the query cache the client is connected to. Reference
Now to get the active query keys, call the method getAll() in the queryCache and this will return an array of Query. Now can access the QueryKey.
const queryCache = queryClient.getQueryCache()
const queryKeys = queryCache.getAll().map(cache => cache.queryKey) // QueryKey[]
Related
I'm trying to update 2 firebase collections that will contain an array element that is the same.
For example, I'm building a job app, so when a user creates a job, it pushes that job object into a firebase collection called alljobs under a document called alljobs. In addition, the same job is pushed to a firebase collection called created jobs. Where each user on the app has their individual created jobs, each doc is named the users id.
Is there an easy way to update this specific job in both alljobs collection and the createdjobs collection?
For example, my approach of doing it would be like this.
Individual Job component (obtained by previously mapping through all the jobs)
const [userjobs, setUserjobs] = useState([])
const {job, createdjobs} = props
function updateJob(){
createdjobs?.map(job1=>{
if(job1.jobid===job.jobid){
const jobindex = createdjobs.indexOf(job1)
createdjobs[jobindex].jobtitle = 'New title'
db.collection('createdjobs').doc(user.uid).update({
jobs: createdjobs
})
}
})
}
I'll basically have to repeat this same process to update once again the job that has just been updated in the createdjobs collection. This gets repetitive and messy. So looking for a solution to this. By mapping through alljobs this time.
useEffect(()=>{
db.collection('alljobs').doc('alljobs').onSnapshot(snap=>{
setAlljobs(snap.data().jobs)
})
},[])
There is no shortcut for your problem I think. But I suggest you to write a sync function to Firebase Function.
It will watch changes of one source and sync to others. So that your logic code only needs to manage one source of trust.
As #Thanh Le suggested you can write a Google Cloud Function to fulfill this purpose. In cloud functions there is a function type named triggers. You can use this triggers.
Cloud Function triggers
We can write functions which will automatically trigger when the specfied document or set of documents,
onCreate - Trigger when document creating
onUpdate - Triggered when a document already exists and has any value changed.
onDelete - Trigger when document deleting
onWrite - Triggered when onCreate, onUpdate or onDelete is triggered.
From these triggers you can use onWrite Trigger to to implement the function.
exports.updateAllJobsTrigger = functions.firestore.document('createdJob/{userId}')
onWrite(async (change, context) => {
// Check if document is deleted
if (!change.after.exists) {
logger.log('Document not existing. Function exited.');
return;
}
const allJobsRef = admin.firestore().collection('alljobs').doc('alljobs');
// Check if document created
if (!change.before.exists) {
try {
// This is a newly created document. Therefore newjob should be in first element
const newJob = change.after.data().jobs[0];
const data = (await allJobsRef.get()).data();
if (data) {
const jobs = data.jobs;
await allJobsRef.update({
jobs: [...jobs, newJob]
});
logger.info('Job added to All jobs queue.');
}
} catch (exception) {
logger.error(exception)
}
return;
}
try {
// This is a updating document.newly added job is in the last element of the array
const newJob = change.after.data().jobs[change.after.data().jobs.length - 1];
const data = (await allJobsRef.get()).data();
if (data) {
const jobs = data.jobs;
await allJobsRef.update({
jobs: [...jobs, newJob]
});
logger.info('Job added to All jobs queue.');
}
} catch (exception) {
logger.error(exception)
}
});
As #Nimna Perera said, you can use Cloud Functions to solve this issue. Your CF should be triggered when a document is updated, created or deleted (so the onWrite option). Another way to do this is through transactions, when you need to read and write the documents or batched writes when you only need to write in one or various documents. In both cases you are not limited to a single collection, so it should work for your issue.
This must be user error, but I've got an app with a simple currentUser query that looks at a JWT for an id, looks it up, and returns the appropriate user.
I can look at devtools and see that it's in the cache as __ref:User:19
export const CURRENT_USER_QUERY = gql`
query{
currentUser {
id
fullName
email
}
}
`
But in my component, when I do const { currentUser } = client.readQuery({ query: CURRENT_USER_QUERY }); it (intermittently) blows up because:
Cannot destructure property 'currentUser' of 'client.readQuery(...)' as it is null.
User:19 exists and is all the stuff I need. I hit reload, and that same page works.
I have my default error policy set to "all" in the client.
This is version 3.3.11 of the client. Chrome v88.04, for what that's worth. React 17.01.
Am I missing something here? Should that not return a value synchronously (and dependably) if that item's in the cache?
Is there a better way to deal with that situation? I'm trying to move this app away from storing things in redux or some context provider since it's already being handled by Apollo. Seems redundant to have that responsibility handled by a bunch of different things.
I was facing issues with .readQuery() last night. I was getting null returned everytime, though the logic was right. I was calling .readQuery() within a component I imported into my React page.
What ended up being my issue is that I was not updating the same query I made in the "parent" react page as the one in the component.
I don't know if this is the same problem you're running into, but I thought I'd leave this here for perpetuity and perspective.
I was facing the same issue. I fixed it like this:
const existingData = cache.readQuery({
query: GET_CONTRACT,
variables: {
...variables, // fixed: passing the same reference to variables
},
});
The problem here can be in new variables or absence of variables. So the query was made with variables and you try to get it from the cache without. .readQuery from cache must be identical
You need to pass the same variable with the same values you use when executing the mutation and in the same order
existingData = cache.readQuery({
query: QUERY_NAME,
variables: {
x: mutationValue1,
y: mutationValue2
z: mutationValue3
},
});`
I'm building a react app, I have a datasheet and update directly on the data stream
I want when I send data to graphql server for update and get true result then I will update apollo cache with cache.writeQuery
The problem is that when the following code is executed, there is also a request to the graphql server to get the data from the whole table and update the cache, I don't want to request to the graphql server to work. There, I want to update from the browser. So where did I go wrong?
here is my code
updateInventoryCache: async (_, { inventory, productId, variables }, { cache }) => {
let variablesData;
if (variables) {
variablesData = JSON.parse(variables);
}
const { getListProduct } = cache.readQuery({
query: GET_PAGING_PRODUCT,
variables: variablesData.variables
});
cache.writeQuery({
query: GET_PAGING_PRODUCT,
variables: variablesData.variables,
data: {
getListProduct: {
...getListProduct,
products: getListProduct.products.map((product) => {
if (product.id === productId) {
return {
...product,
inventory
};
}
return product;
})
}
}
});
return true;
}
"#apollo/client": "^3.3.7"
update 1:
I will initially call the graphql server to get the data and store it in apollo's (cache-and-network) cache. Then I want to update that data in the cache without having to call the apollo server to refetchQueries As in the post, I used the client.writeQuery function to update the cache but instead of updating at the client, apollo called the graphql server to get new data and update the cache while I was not using refetchQueries.
update 2:
I checked, my cache has been updated but my UI doesn't re-render
I believe what you're looking for is nextFetchPolicy="cache-first":
https://www.apollographql.com/docs/react/data/queries/#usequery-api
nextFetchPolicy
FetchPolicy to begin enforcing
after the current request. Useful for switching back to cache-first
after cache-and-network or network-only.
After your call to cache.writeQuery your datasheet query will then check the cache-first to see if all of it's required data is there. If you get a cache hit, it will return data immediately without loading.
Keep in mind with AC3, sometimes multiple queries can share the same cache object but request different fields. If either cache.writeQuery or your typePolicies merge causes an active query field to be missing from the cache, it will result in a cache miss for that query. I.e. An active query that once had valid data, will suddenly return undefined. More on that issue here.
Use fetchPolicy="cache-only" to use only the cache https://www.apollographql.com/docs/react/data/queries/#setting-a-fetch-policy
I am trying to make a request to get all the events of a user, then get the detail of this events in a list. I don't find a right solution to do that.
Database
Actions index
So at the moment, I only get the user's travel, but not the detail of each event that the user have.
Thank you for your help
You'll need to do another on() or once() for each event inside your current callback, and load the additional data. This process is known as a client-side join. Then within the inner loop you can dispatch the results, either on each load or when all are loaded.
Code (untested, so there may be typos):
usersRef.child(uid).child("events").on("value", snapshot => {
var promises = []
snapshot.forEach(eventSnapshot => {
promises.push(eventsRef.child(eventSnapshot.key).once("value"));
})
Promise.all(promises).then(eventSnapshots => {
// eventSnapshots contains the details of all events
return eventSnapshot.map(eventSnapshot => eventSnapshot.val());
}).then(events => {
dispatch({ type: FETCH_EVENTS, payload: events });
});
})
Alternatively you can duplicate the minimal data for each event under /users/$uid/events/$eventid`. This duplicates data, but prevents the need for client-side joins. This type of read-vs-write trade-off is very common when using NoSQL databases. For strategies for the duplicated data up to date, see How to write denormalized data in Firebase.
I have a relay mutation that posts some data to my server. My app shouldn't wait for the response before continuing.
I know I can execute arbitrary queries with the following:
const query = Relay.createQuery(Relay.QL`
query {
viewer {
searchInterests(prefix: $prefix, first: 10) {
edges {
node {
id
name
}
}
}
},
}
`, {prefix: input});
Relay.Store.primeCache({query}, readyState => {
if (readyState.done) {
// When all data is ready, read the data from the cache:
const data = Relay.Store.readQuery(query)[0];
...
}
How can I fire off mutations asynchronously without my app waiting for the response?
When designing a fat query, consider all of the data that might change as a result of the mutation – not just the data currently in use by your application. We don't need to worry about overfetching; this query is never executed without first intersecting it with a ‘tracked query’ of the data our application actually needs. If we omit fields in the fat query, we might observe data inconsistencies in the future when we add views with new data dependencies, or add new data dependencies to existing views.