-
Notifications
You must be signed in to change notification settings - Fork 306
Improve useList efficiency #90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve useList efficiency #90
Conversation
Use the `.once`’s `val()` to determine the number of children, then batch that many calls to the `child_added` handler into one `value` dispatch, resulting in one additional render instead of one per child. Fix shadowing of `query` variable, use `ref` instead. Fix the `ref.off` calls to pass the right function to unsubscribe. Fix dependencies on useEffect. Fixes CSFrequency#74
Note: since it looks like PRs can take some time to get merged, if anybody uses this code you'll also want the changes from PR #79 or you'll still get unnecessary renders. |
I was thinking ways to fix this myself. I was thinking on collecting the data from the As for the delays on maintenance you might want to follow #93 and see what comes out. |
A couple of thoughts: Perhaps it would be good to enable this option with a Instead of counting, wouldn't it be safer to have, besides returning the results of the initial load at once, the following line in const addChild = (
currentState: KeyValueState,
snapshot: database.DataSnapshot,
previousKey?: string | null
): KeyValueState => {
if (!snapshot.key) {
return currentState;
}
const { keys, values } = currentState;
// add this:
if (keys.includes(snapshot.key)) {
return currentState
}
// ... Somehow I think this might also fix an issue I have with developing an application, where the hot reload of the changed source code does not seem to dispatch the Also, it seems to me better to have this check done in the reducer itself rather than Thanks and regards |
This library is an abstraction so that you don't have to worry about
I consider the excessive renders to be a bug, not something that needs backward compatibility.
I've addressed that in my comment on #74, but the gist is that the object returned by And the counting is still safe because we know |
As for making fast loading an option, the hook should offer the same features as the functionality it wraps, and individual calls is one option. Some UI designers prefer to have the users entertained with some screen action rather than waiting a fraction of a second with a static display while the data shows up. They go for the perception of speed rather than actual speed, and my guess is that is the reasoning behind offering both options on Firebase. I wouldn't go for that bogus perception option myself, but I wouldn't consider it a bug, just a silly option I wouldn't go for. And, indeed, the count should work, the relevant sentence in https://firebase.google.com/docs/reference/js/firebase.database.Reference#child_added-event is:
I read it many times and still don't fully trust it, but that is just me. I feel they might just be warning developers that it would duplicate the data on the first |
I think it abstracts and simplifies rather than wraps and exposes firebase’s way of handling events. It effectively becomes an efficient
It wasn’t a fraction of a second, at all. I had it fetching a list of 500 items and it took dozens of seconds to finish rerendering them all. The first child is rendered 500 times, the second 499, the third 498, etc. Instead of rendering children 500 times as it would with one initial load it renders children 125,250 times. It’s incredibly faster with the PR code. The hook could expose variables for how many items exist and how many have been loaded for UI progress bars, but that will again cause one render per child (albeit a more efficient one since the child isn’t present yet).
This shouldn’t matter. If 500 children exist on load and an event for child #501 is fired out of order while processing them then children 1-499 and 501 will exist in the first render followed immediately by a render with child 500 added (or whatever child is fired last). |
I was poking fun at the users who can't wait for their devices just to do something whom the UI designers have to cater to. My guess is that the Firebase guys fire so many events to satisfy that need. I know your PR saves more than a fraction of a second, orders of magnitude more than that! That is why I love it. As for the soundness of the counting strategy, imagine this situation:
The hook would ignore item 4 and report item 9 twice. Is this scenario possible? I don't know, nothing assures me it is not possible. For all it knows, it fired Obviously, the other alternative is that the |
Using your example one of two things would happen: If firebase preserves the order of events:
If firebase does not preserve the order of events:
In both cases the final render is correct. |
To expand on that a little bit, because it's using a count and not keys it doesn't matter which children are fired, just that that many children do get fired. Any children that fire the event afterward will cause one render per event, as expected. |
No, that might not be the case. It might be:
The item with key 4 would have never been inserted into the The highlighted 9 in the second render would actually belong to the first |
How would 4 be ignored? And how would 9 appear twice? The initial data from I welcome a test to prove the behavior you’re describing, but I do not think it is possible with the PR’s code. |
Finally, I got it. Sorry for all the trouble. Indeed, you are reading all the data on all the Sorry and thanks for your patience. |
Glad it makes sense (: And for some context on throwing away the |
I think I have an idea that might help, at least it will compensate for all the trouble I gave you, so I hope. Somewhere above I said:
I was referring to
Now, lets go to the reducer.
At the end of the return [state.value.values, state.loading, state.error]; Do: return [
state.childrenToProcess ? undefined: state.value.values,
state.loading,
state.error
] What do you think, would that work? Isn't it simpler? It would make I am sorry I cannot test any of this as I seem to be one of the affected by this issue. Opened more than a year ago and still active as recently as 3 weeks ago with 52 replies, I tried all that has been suggested there (except downgrading the nodejs version) and still can't make it work. It works if I simply switch to a newer Firebase, which is the package that depends on this gRpc, but then I would be testing a completely different package, so I'm stuck there. I am still unsure if that change to current firebase 8.x.x would make any difference, but I was trying to go with the original as it is now. It seems to use only the type definitions from firebase and those don't seem to be any different so I should be fine. |
No, it would not. It would help is I kept my mouth shut. |
lol, I enjoy debates and code reviews, it's fine :p I think the event is the right place to handle it though, rather than the reducer. You're always welcome to implement the reducer method as a PR and the maintainer, should they ever return, can choose which they prefer. Personally, I think I'll be switching over to reactfire due to the stagnancy on this repo. |
@caleb-harrelson thanks for the submission and apologies that it's taken longer than I'd like to review this properly. In short, it's been a tough year! Previously, Firebase would only fire the For this PR to work correctly, the behaviour must have changed as otherwise the I'll do some testing, but if this behaviour has changed, then this is a nice way to resolve the issue. |
@chrisbianca thanks for coming back and injecting some life into the repo! Here's to a better 2021. |
@caleb-harrelson thanks again for this submission. I've done some testing and identified one change that was needed to ensure that the hook correctly updated if the supplied list reference changed. I'll get this merged now and plan on releasing a version 3.0.0 at some point in the next week once I've been through and addressed some more of the issue list. |
@chrisbianca is this not live in the main package yet? Would be useful for me. |
@caleb-harrelson @chrisbianca I forked the repo because I wanted to use the latest code in my app (no release has been made yet) but I noticed some bugs with the new library. Specifically, if I have a list with 0 values and write to it quickly and also subscribe to it on the screen at the same time, that 1 item never comes through. If I restart the app it shows up. Guessing something could be off with the joint once / child_added logic. For now though I must stick with the latest npm release and wanted to bring this to your attention. |
@achuinard thanks for the report. You're right, there was an issue with initialisation when the list is initially empty. I've just pushed up a fix to master - would you be able to give it a try to confirm that this fixes your issue? If so, I'll look at getting this cut into a release |
This has now been released in https://github.com/CSFrequency/react-firebase-hooks/releases/tag/v3.0.1 |
.once
’sval()
to determine the number of children, then batch that many calls to thechild_added
handler into onevalue
dispatch, resulting in one additional render instead of one progressively slower render per child.query
variable, rename toref
instead.ref.off
calls to pass the right function to unsubscribe.useEffect
.Fixes #74