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.
Related
I am using express to create my firebase functions, and I understand how to create regular callable functions. I am lost however on the exact way to implement trigger functions for the background (i.e. onCreate, onDelete, onUpdate, onWrite), as well as how Reactjs in the frontend is supposed to receive the data.
The scenario I have is a generic chat system that uses react, firebase functions with express and realtime database. I am generally confused on the process of using triggers for when someone sends a message, to update another user's frontend data.
I have had a hard time finding a tutorial or documentation on the combination of these questions. Any links or a basic programmatic examples of the life cycle would be wonderful.
The parts I do understand is the way to write a trigger function:
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
.onWrite((change, context) => {
// Only edit data when it is first created.
if (change.before.exists()) {
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
// Grab the current value of what was written to the Realtime Database.
const original = change.after.val();
console.log('Uppercasing', context.params.pushId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to the Firebase Realtime Database.
// Setting an "uppercase" sibling in the Realtime Database returns a Promise.
return change.after.ref.parent.child('uppercase').set(uppercase);
});
But I don't understand how this is being called or how the data from this reaches frontend code.
Background functions cannot return anything to client. They run after a certain event i.e. onWrite() in this case. If you want to update data at /messages/{pushId}/original to other users then you'll have to use Firebase Client SDK to listen to that path:
import { getDatabase, ref, onValue} from "firebase/database";
const db = getDatabase();
const msgRef = ref(db, `/messages/${pushId}/original`);
onValue(msgRef, (snapshot) => {
const data = snapshot.val();
console.log(data)
});
You can also listen to /messages/${pushId} with onChildAdded() to get notified about any new node under that path.
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.
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'm trying to implement short-term caching in my Angular service -- a bunch of sub-components get created in rapid succession, and each one has an HTTP call. I want to cache them while the page is loading, but not forever.
I've tried the following two methods, neither of which have worked. In both cases, the HTTP URL is hit once for each instance of the component that is created; I want to avoid that -- ideally, the URL would be hit once when the grid is created, then the cache expires and the next time I need to create the component it hits the URL all over again. I pulled both techniques from other threads on StackOverflow.
share() (in service)
getData(id: number): Observable<MyClass[]> {
return this._http.get(this.URL)
.map((response: Response) => <MyClass[]>response.json())
.share();
}
ReplaySubject (in service)
private replaySubject = new ReplaySubject(1, 10000);
getData(id: number): Observable<MyClass[]> {
if (this.replaySubject.observers.length) {
return this.replaySubject;
} else {
return this._http.get(this.URL)
.map((response: Response) => {
let data = <MyClass[]>response.json();
this.replaySubject.next(data);
return data;
});
}
}
Caller (in component)
ngOnInit() {
this.myService.getData(this.id)
.subscribe((resultData: MyClass[]) => {
this.data = resultData;
},
(error: any) => {
alert(error);
});
}
There's really no need to hit the URL each time the component is created -- they return the same data, and in a grid of rows that contain the component, the data will be the same. I could call it once when the grid itself is created, and pass that data into the component. But I want to avoid that, for two reasons: first, the component should be relatively self-sufficient. If I use the component elsewhere, I don't want to the parent component to have to cache data there, too. Second, I want to find a short-term caching pattern that can be applied elsewhere in the application. I'm not the only person working on this, and I want to keep the code clean.
Most importantly, if you want to make something persistent even when creating/destroying Angular components it can't be created in that component but in a service that is shared among your components.
Regarding RxJS, you usually don't have to use ReplaySubject directly and use just publishReplay(1, 10000)->refCount() instead.
The share() operator is just a shorthand for publish()->refCount() that uses Subject internally which means it doesn't replay cached values.
I am trying to build an application using GraphQL and React Relay.
As part of this I have created a root query with the following specification:
query {
AllServices {
edges {
node {
id
}
}
}
}
So I have created a mutation - which works well - called CreateGithubService.
Here is a snippet from the Relay mutation:
getFatQuery() {
return Relay.QL`
fragment on CreateGithubServicePayload {
createdService
}
`
}
getConfigs() {
return [{
type: "REQUIRED_CHILDREN",
children: [
Relay.QL`
fragment on CreateGithubServicePayload {
createdService {
id
}
}
`
]
}]
}
The problem I have is that this is not causing the views which rely on the information to update.
My AllServices query is not refetched and I am unable to specify it in the Fat Query.
How can I setup my mutation to add the element to the all services query?
Thanks!
REQUIRED_CHILDREN will not update the local graph; it's strictly for fetching extra data that is only used in the success callback of the mutation update. In fact, it's not even documented outside of the source code, so I'm not sure how you decided to use it like this...
Although you haven't said so, I assume you want this new node (createdService) to be added to the AllServices connection? If so, you need to tell Relay that your mutation affects that connection:
At the moment, Relay basically assumes all mutations affect nodes, not arbitrary queries. To my knowledge, you won't be able to configure a mutation to update a non-node root query. Therefore, you should add a node at the root that acts as the parent of your AllServices connection (the convention for this is typically viewer). In other words, make it work like this: query { viewer { AllServices { ... } } }
Your mutation payload should always return everything that has been changed, not just the new data. That means you need a way to refetch the AllServices connection from the payload. Once you add the viewer node to your schema, you can return that node in the mutation payload and change your fat query to specify that the connection has changed, e.g.: fragment on CreateGithubServicePayload { viewer { AllServices } }
Given those two schema changes, you can then configure your mutation using FIELDS_CHANGE and specify the fieldIDs like { viewer: this.props.viewer.id }