Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Implement "infinite scroll" for expenses #95

Merged
merged 12 commits into from
May 30, 2024

Conversation

shynst
Copy link
Contributor

@shynst shynst commented Feb 18, 2024

Motivation

As @scastiel mentioned in #30 the expense list gets quite slow when dealing with a large number of transactions. I have managed to import my "home expenses" group over from Splitwise which dates back to 2018 and features around 3000 expenses. To convert my expenses from Splitwise I used the script from @ChristopherJohnston as starting point.

Whenever I was entering this group I needed to wait for about 15 seconds until the entries appeared. When it was fully loaded the UI felt a bit sluggish - buttons took some time to respond to clicks.

Therefore I was motivated to implement some sort of incremental expense loading. As it is 2024, I went for a technique called "infinite scroll" over traditional "old-school" pagination. Infinite Scroll is a technique that automatically adds the next page as the user scrolls down through content, more content is loaded. User don’t need to wait for pages to preload. So infinite scroll can provide user a better experience.

Implementation

Changes to component Page

  • only load the first 200 expenses (if there are 200 or more)
  • pass preloaded expenses and total expense count to child component ExpenseList

Changes to component ExpenseList

  • Extract ExpenseCard (the component that renders an expense list item) from ExpenseList
  • If there are more expenses to show:
    • append preloading "skeletons" to end of list
    • fetch more expenses when last item in list comes into view (using react-intersection-observer)
    • after each fetch increase number of fetched expenses by 1.5 (*)

(*) Database fetch is usually not the issue here, but the length of the list is. The longer the list gets, the longer React needs to redraw. To limit the number of redraws, the next query fetches 1.5 times more items. Therefore, when the list is still short, we add less items (which is fast). The longer the list gets, the more items are added. This is slower, but the list will be completed with less redraws (which is less painful).

Links

Some resources that helped me implementing infinite scroll

Remarks

  • dramatic speedup for long lists (startup time is down from 15 to 2 secs)
  • for 99% we don't need to load more that the most recent 200 entries
  • UI feels a lot more responsive, too
  • no "load more" button or "old-school" pagination needed
  • works very well with the search button, too

Enjoy! Hope you like it!
Comments and suggestions are always welcome.

- display only this year's entries by default
- a "Show more" button reveals all expenses
- getPrisma() is not async and doesn't need to be awaited
- turn getPrisma() into exported constant "prisma"
- make JSON more concise and less redundant
- some properties were removed (i.e.instead of "expense.paidById" use "expense.paidBy.id")
- no need to search for participant by id to get it's name
- name property is already present in expense
- specify offset and length to get expenses for [offset, offset+length[
- add function to get total number of group expenses
- in server component Page
  - only load first 200 expenses max
  - pass preloaded expenses and total count

- in client component ExpenseList, if there are more expenses to show
  - test if there are more expenses
  - append preloading "skeletons" to end of list
  - fetch more expenses when last item in list comes into view
  - after each fetch increase fetch-length by factor 1.5
    - rationale: db fetch usually is not the issue here, the longer the list gets, the longer react needs to redraw
@shynst shynst mentioned this pull request Feb 18, 2024
@ChristopherJohnston
Copy link
Contributor

This is a great contribution! I also have a split wise-imported expense list (via a script) with similar issues as a result of its size.

nb would also be good to share how you imported - my script is here: https://gist.github.com/ChristopherJohnston/a6e69bd8894f20ecfe127fa4149bd013

@shynst
Copy link
Contributor Author

shynst commented Feb 27, 2024

Thank you for your comment! And thank you too for providing me with your script that served me well to import my expenses from Splitwise. I updated my PR and mentioned your contribution.

@shynst
Copy link
Contributor Author

shynst commented Mar 4, 2024

@scastiel : I think this is a good contribution to spliit. Did you have time to look over it? Looking forward to your input.

@ChristopherJohnston
Copy link
Contributor

@scastiel - any comments/concerns on this?

@dcbr dcbr mentioned this pull request Apr 14, 2024
@ChristopherJohnston
Copy link
Contributor

@scastiel the app is definitely in need of something like this. It is very slow to render the expense list when there is a reasonably large number of expense items (ie 1000+)

@ChristopherJohnston
Copy link
Contributor

@shynst are the merge conflicts simple to resolve? This might help things along

- solved conflict in:
  `src/app/groups/[groupId]/expenses/expense-list.tsx`
- occurred, because `ExpenseCard` was extracted from `ExpenseList` in #107dc8
@shynst
Copy link
Contributor Author

shynst commented May 26, 2024

@ChristopherJohnston should be ready to merge now!

@ChristopherJohnston
Copy link
Contributor

Thanks @shynst! @scastiel - when do you think you will be able to catch up on these PRs?

@scastiel
Copy link
Member

Amazing @shynst!

@scastiel scastiel merged commit d3fd802 into spliit-app:main May 30, 2024
1 check passed
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants