Skip to content

Conversation

@HendrikThePendric
Copy link
Contributor

@HendrikThePendric HendrikThePendric commented Jan 12, 2026

Context / Problem description

We @dhis2/analytics-frontend have a few places where we lazy-load options into Transfer components. This wasn't always working and the problem couldn't really be solved in the app.

As can perhaps be seen by the change in PR title and the difference in implementation between commits, it took me a while to get a clear picture of whether this was a bug, of if we needed a new feature which would allow apps to solve the problem. In the end, I settled on the following problem evaluation:

  • The onEndReached prop is a callback that the Transfer should call when "the end of a list is reached". There is some ambiguity here.
  • Prior to this PR the implementation was solely based on an IntersectionDetector subscription which would fire when there is a change in the isIntersecting value and the new value is false. Effectively this means the callback only fires under two circumstances:
    • When the component subscribes to the IntersectionDetector (so on mount) and the list end is in view
    • When the list is long and the user scrolls down, bringing the list end nearly into view (the detection element has a height 50px, so 50px before the end to be exact).
  • What the prior implementation did not take into account were lists that grow gradually and happens to stay "within the viewport" in these cases the list changes in length and the IntersectionObserver (browser API) would actually notify it's subscribers, but our IntersectionDetector component only calls the provided callback when a change is detected and since the value of isIntersecting was true and remains true this won't happen. The problem for apps using onEndReached for lazy loading is that now the process gets stuck: a new page actually needs to be fetched but the trigger for doing so is not called.
  • There is another interesting edge case which is when a number of items are added to the options array, but all of these options are selected. This means none of them will be added to the "source list" and the source list will remain static, and the IntersectionObserver itself would not be of any use here either.

So my final conclusion was: this is a bug, the correct behaviour for calling onEndReached is that it happens in two cases:

  • As before, when the user scrolls towards the end of the list
  • But also (new), when options are added and the list end is in view

Solution

To detect events where options are added we simply monitor the options array length. And when we detect this, we check if the list end is in view and if so, we call the callback.

While this sounds simple enough, it's not so easy to do in React. The Transfer has actions to the "raw" options, but not the elements that need to be measured. The OptionsContainer does have access to these DOM elements, but here the options have already be filtered which would mean that we'd fail the edge case I mentioned above.

I solved this problem by adding a context provider. After writing the above, I realised that I could have possibly also solved this in the OptionsContainer itself by passing an optional allOptionsCount prop from the Transfer. I guess that solution would have been a bit simpler, but adding a context does provide a fairly clean solution, and it could also be a way to implement other functionality in and ergonomic way.

Notes

  • I think one of the underlying issues here is that this is a monolith component so everything needs to be solved within the component. If the component was more composable, we may have been able to solve this in the app instead of the component.
  • I need some guidance on the API docs and E2E tests

Checklist

  • API docs are generated
  • Tests were added
  • Storybook demos were added

@HendrikThePendric HendrikThePendric requested a review from a team as a code owner January 12, 2026 13:44
@dhis2-bot
Copy link
Contributor

dhis2-bot commented Jan 12, 2026

🚀 Deployed on https://pr-1715--dhis2-ui.netlify.app

@dhis2-bot dhis2-bot temporarily deployed to netlify January 12, 2026 13:47 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify January 13, 2026 14:16 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify January 13, 2026 14:30 Inactive
@HendrikThePendric HendrikThePendric changed the title feat(transfer): add option container resize callbacks fix(transfer): ensure onEndReached is called when the options change and the list end is visible Jan 13, 2026
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
1 New issue
1 New Code Smells (required ≤ 0)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants