Subscription Refetch Updating Data - reactjs

I wanted to get your opinion on something.
I'm trying to understand how a subscription works. however, I couldn't find a way to pull an array of objects in a subscription. for example, if I use createMany, I can not return all the result via subscription.
The second issue is if I return a single item for example if it's a new item, I have to "manually (air quote)" add that item to the list that is already displayed. But this feels to me I don't actually display real-time true data.
So my question is using something like
useEffect(() => {
// refetching original query when subscription is triggered
refetch();
}, [updatedNotificationData]);
would there be any downside like hitting up the server more than I should? let's say every time there is a refetching happens I might be pulling thousands of notifications (I know there is caching but still) or is there a better way to deal with bringing new data.
Also, I tried adding subscribed data to the original list but for some reason react adds 2 of the same item every time.
Thanks in advance if you can give me in the right direction.

if I use createMany, I can not return all the result via subscription.
That shouldn't be a problem if you define the return type of the subscription as array.
type Subscription{
onChange:[ObjectType]
}
It would allow you to avoid fetching again but updating cache can get a bit complicated.
Also, I tried adding subscribed data to the original list but for some reason react adds 2 of the same item every time.
In case you are using the the subscribeToMore method it's not really reacts fault but the way how the updateQuery method works: github issue regarding this.
My workaround was to subscribe via the useSubscription hook and handle the cache modifications inside the onSubscriptionData callback apollo documentation and also setting the useQuery hooks skip option once I get the data so it wont query on each rerender.

Related

Efficient Firebase document reading and querying with hooks

I am currently using queries to get the initial state of a user. They potentially could have thousands of individual documents that don't need to be set up via hooks.
My current approach is mostly working, however it does mean there is a double read on the documents that I create the hook on.
query(
collection(firestore, "userDocs"),
where("ownedBy", "==", userId),
orderBy(documentId()),
limit(6)
)
This is my initial query, I am using react infinite scrolling to then fetch more as they scroll down. Each of these documents is a rendered component in React.
Within that component I then setup a hook to pretty much go and get the same data that I just did.
setCol(doc(firestore, "userDocs", userDocId),{
snapshotListenOptions: { includeMetadataChanges: true },
})
They both set a document state on the component, once on load, and when ever the firebase hook goes off. I am using a react firebase package here for hook simplification.
My question is, how can I get this hook functionality which I really need, but stop this initial read from the firebase hook. If I don't do the initial query for the documents, I don't get the data I need to make the hooks. And also an approach I tried of just using hooks passed to the user doc component was much slower than my query based approach because firebase queries are really fast than singular document reads.
I am basically trying to prevent a second read when the hook on the document is created because its data will just be the data I got back from the query and isn't very efficient. I have seen some implementations that get around this using a timestamp updated at field, but does that require me to perform an extra write on the documents that I've just queried?
When I update the document that triggers the hook its possible I can pop a timestamp field in then but Im not sure how that solves my initial read problem.
Many thanks for any suggestions :)

How can I get updates from a Firestore document? ReactJS

I have the following problem:
I have a document with data in firestore. I collect the data from that document and show it on the screen, everything is perfect. But, if I add data or modify it, how can I get the new data at the moment the document has been updated, without continuously checking if there have been changes? Since it would exceed the firestore reading limit. That is, it receives a kind of notification that there is new data, checks it and updates itself. I've tried with a state variable(useState) inside a useEffect but I can't find a way to make it work.
I hope you can help me.
first at all try to make a get right after your create or update action on the document.
If you don't want to do that, you can read this part of the documentation of firestore and try to create a custom hook calling it directly on the useEffect (in order to get every update in the lifecycle).

What is the useState alternative for fast parallel, async state updates?

I'm trying to build a little calendar, where one can "mark" days by clicking on them.
Each click causes a request to the server to persist the change (set or unset a day).
It seems like useState can only keep up with so many changes at once, and due to the nature of reloading the component, i loose some of the fetch-requests happening as well.
When i understand the useState behavior correctly, each setDays will reload the whole Calendar, even if an instance still has a request running. The system is smart enought, so that a (limited) number of requests still manage to complete in the background and trigger their state update. However i have no control or guarantee over how many "make" it when clicking fast.
My real code has an additional state change, by marking/unmarking each day as "in flight" (via dayClassName) while the request is running, probably increasing the problem even more.
I'm a bit lost in what direction to go from here:
Should i try to limit the effect of a day change to a single day itself, avoiding to update the whole calendar with every click (need the result outside, though).
Or is a different system/strategy to manage the state, e.g. redux, the better choice here. For example to serialize the updates into one stream.
Update:
Here is a codesandbox with example: https://zpvy0.csb.app/
I tried to get as close to the real thing as possible, unfortunately i still can't reproduce the issue. It seems like react/useState is not the issue, as in the codesandbox it works reliable with 30+ requests triggered at once.
Update 2:
I have rewritten my code using the codesandbox version as base (re adding what other functionality/styling, etc was there). Now everything works perfectly.
Currently i have no idea what difference was causing it at the end.
If I'm understanding your issue correctly it sounds like the issue is that addDay and removeDay are called in quick succession and you are losing some state updates. You mention users clicking "to fast" so it may be the case that more than 1 state update is being enqueued. Since you are using plain updates if 2 updates are enqueued within the same render cycle the second update overwrites the first. If more get enqueued then each subsequent processed update overwrites the previous. Hopefully you get the idea here.
The resolution for this is to use functional state updates so each enqueued and processed update updates from the previous state, not the state the update was enqueued in. This means if multiple updates are enqueued in a render cycle each update/change is applied sequentially and the result aggregated state update is available for the next render cycle.
Functional Updates
If the new state is computed using the previous state, you can pass a
function to setState. The function will receive the previous value,
and return an updated value.
The previous state is an array and you are updating from that array when appending new day objects. It's a very minor tweak.
const addDay = async (day) => {
await makeRequest(day);
setDays(days => [...days, day]);
};
const removeDay = async (day) => {
await makeRequest(day);
setDays(days => days.filter((d) => d !== day));
};

React Query useInfiniteQuery invalidate individual items

How can I invalidate a single item when working with useInfiniteQuery?
Here is an example that demonstrates what I am trying to accomplish.
Let`s say I have a list of members and each member has a follow button. When I press on to follow button, there is a separate call to the server to mark that the given user is following another user. After this, I have to invalidate the entire infinite query to reflect the state of following for a single member. That means I might have a lot of users loaded in infinite query and I need to re-fetch all the items that were already loaded just to reflect the change for one item.
I know I can change the value in queryClient.setQueryData when follow fetch returns success but without following this with invalidation and fetch of a member, I am basically going out of sync with the server and relying on local data.
Any possible ways to address this issue?
Here is a reference UI photo just in case if it will be helpful.
I think it is not currently possible because react-query has no normalized caching and no underlying schema. So one entry in a list (doesn't matter if it's infinite or not) does not correspond to a detail query in any way.
If you prefix the query-keys with the same string, you can utilize the partial query key matching to invalidate in one go:
['users', 'all']
['users', 1]
['users', 2]
queryClient.invalidateQueries(['users]) will invalidate all three queries.
But yes, it will refetch the whole list, and if you don't want to manually set with setQueryData, I don't see any other way currently.
If you return the whole detail data for one user from your mutation, I don't see why setting it with setQueryData would get you out-of-sync with the backend though. We are doing this a lot :)

Automatically calling action AFTER redux state is loaded

I have a app in which users can build a "Quote". So far I've been adhering to "idiomatic" redux (or something close to it) and it's been working out well.
However, I'm struggling to deal with a fairly simple scenario:
When the page is first opened, I fire an async LOAD event which retrieves info from the server needed to build the quote (products, inventory, previously saved line items, etc).
I need some way to be able to automatically add a specific line item(s) to the quote first it's first opened.
I've added a defaultLineItems property to my LOAD payload, but to fire the addLineItem(product, inventory, options) action, I need data from the productReducer, inventoryReducer, optionsReducer. The lineItemReducer could look at these defaultLineItems and try to set it's state appropriately, but that would require having to rewrite a lot of BL typically handled by the actions using data aggregated from reducer memorized "selectors" (EG: defaulting price, quantity, currency translation, etc)
I can think of a couple ways to achieve this, but they all seem somewhat hack-ish (IE storing a flag in the reducer that says I need to fire an action and then running it when my root component props update). It seems like a react component should not be responsible for this type thing.
What is the proper way to do something like this?
Seems there are a couple different ways this can be accomplished but for me the most balanced approach between simplicity and design was to use store.subscribe in conjunction with a reducer to track the last action(s).
At it's simplest, this would look something like this.
store.subscribe(function() {
let state = store.getState();
if(state.lastAction.type === ActionKeys.LOAD){
console.log('load action fired!');
}
})
Please be aware that firing an action from store.subscribe will cause recursion so you need to be selective.

Resources