Caching with redux-toolkit's createApi - reactjs

I am caching product data coming from /products/name API using the Products tag and I want to re-fetch only when the product list has been updated.
But I am concerned that I could still get cached data if someone else (another user) has updated the products and I didn't call /add-product API.
Then what's the use case of caching data using tags in using createApi?
...
endpoints: (builder) => ({
getProducts: builder.query({
query: () => `/products`,
provideTags: 'Products',
}),
addProduct: builder.mutation({
query: ({ product }) => ({
url: `/add-product`,
method: 'put',
body: product,
}),
invalidateTags: 'Products'
}),
...

RTK Query's main purpose is to help keep data on the client in sync with what's on the server.
As part of that, the philosophy of RTK is that it's best to re-fetch data from the server any time something may have changed, so that the client always has the most current data.
"Tags" are used to manage relationships between mutations and queries. When you run a mutation, you're telling the server "go update this data". That means the data on the server is now newer than what's on the client, so we should re-fetch the related data and keep the client up to date. By attaching the same tag from a query's providesTags into a mutation in invalidatesTags, RTKQ knows that any time the mutation runs it should re-fetch the related queries.
If you think there's cases when someone else other than this user may have updated the data on the server, you can also configure RTKQ to automatically poll and re-fetch the data on a timer.
More details:
https://redux.js.org/tutorials/essentials/part-8-rtk-query-advanced#cache-data-subscription-lifetimes
https://redux-toolkit.js.org/rtk-query/usage/automated-refetching
https://redux-toolkit.js.org/rtk-query/usage/polling

Related

How to get currently active queryKeys in react-query

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[]

Update the apollo cache without calling the graphql server (apollo v3.3)

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

A more performance-friendly way to use setInterval() in componentDidMount when fetching data

i am struggling pretty hard here to find the right solution. Currently, I am using setInterval() to "poll" my server and retrieve an array of objects. To fetch the data, I am using axios. Here are the pertinent functions:
componentDidMount(){
this.timer = setInterval(() => [this.getData(), this.getCustData()], 1000);
}
componentWillUnmount(){
this.timer && clearInterval(this.timer);
this.timer = false
}
getData = () => {
axios.get('http://localhost:3001/api/v1/pickup_deliveries')
.then((response) => {
this.setState({
apiData: response.data
})
})
.catch((error)=>{console.log(error);});
}
getCustData = () => {
axios.get('http://localhost:3001/api/v1/customers')
.then((response) => {
this.setState({
custData: response.data
})
})
.catch((error)=>{console.log(error);});
}
The application is running so slow and often times, it will completely hang the server which makes the whole application unusable. Currently the array it's fetching has over 1000+ objects and that number is growing daily. If I fetch the data without polling the server, the feel of my application is night and day. I am not quite sure what the answer is but I do know what I am doing is NOT the right way.
Is this just the nature of mocking "polling" with setInterval() and it is what it is? Or is there a way to fetch data only when state has changed?
If I need to implement SSE or WebSockets, I will go through the hassle but I wanted to see if there was a way to fix my current code for better performance.
Thanks for hearing me out.
On the frontend side, my advice would be to not use setInterval, but use setTimeout instead.
Using setInterval, your app might send another request even if the response for previous request hasn't come back yet (e. g.: it took more than 1 second). Preferably, you should only send another request 1 second after the previous response is received.
componentDidMount() {
getData();
}
getData = () => {
fetch().then(() => {
updateState();
// schedule next request
setTimeout(getData, 1000);
});
}
You should also try to reduce the amount of updates that need to be done on the frontend, for example by reducing the number of the data.
But nevertheless, I think the most important is to rethink the design of your application. Polling huge JSON that is going to only grow bigger is not a scalable design. It's bad for both the server and the client.
If what you are trying to do is to have the client be notified of any changes in the server side, you should look into WebSocket. A simple idea is that the browser should establish a WS connection to the server. Upon any updates to the server, instead of sending down the whole data, the server should only send down the updates to the client. The client would then update its own state.
For example, let's say 2 users are opening the same page, and one user make changes by adding a new Product. Server will receive this request and update the database accordingly. It will also broadcast a message to all open WebSocket connections (except for the one connection that added the Product), containing a simple object like this:
{
"action": "INSERT",
"payload": {
"product": {
"id": 123123,
... // other product data
}
}
}
The other user will use this data to update its own state so it matches the server.

How fetch data in from firebase database from two differents sublevel?

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.

How to execute a relay mutation asynchronously?

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.

Resources