You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hi folks so we are facing an issue at Expensify with this lib , On Android/Fabric, react-native-pdf can emit both loadComplete and pageChanged natively, but JS sometimes only receives pageChanged.
This happens because both callbacks are routed through the same TopChangeEvent / topChange event path, and TopChangeEvent inherits React Native's default coalescing behavior.
In practice, the later pageChanged event can replace the earlier loadComplete event for the same PDF view before JS receives it.
We hit this in Expensify while validating local PDF receipts on Android:
If native emits both loadComplete and pageChanged, JS should receive both callbacks in order.
Actual behavior
Native Android finishes loading the PDF, but JS can miss onLoadComplete entirely because the event was coalesced away before delivery.
Why this matters
This breaks the onLoadComplete contract under Android/Fabric.
In our Expensify case, it showed up during PDF validation: native rendered the PDF successfully, but JS never received onLoadComplete, so the app treated the PDF as if validation had not completed.
Root cause
loadComplete and pageChanged are currently treated as the same coalescible event type for the same view:
This is the smallest fix that preserves the existing event model while ensuring both loadComplete and pageChanged reach JS.
Alternative
A larger alternative would be to split these callbacks into distinct native/Fabric events instead of multiplexing them through shared topChange, but that seems unnecessary for fixing the event loss itself.
Validation
i confirmed the behavior locally with temporary native/JS tracing:
before state:
native emitted loadComplete
native emitted pageChanged
JS only received pageChanged
after disabling coalescing:
JS received loadComplete
JS then received pageChanged
That points to event coalescing as the source of the loss.
Hi folks so we are facing an issue at Expensify with this lib , On Android/Fabric,
react-native-pdfcan emit bothloadCompleteandpageChangednatively, but JS sometimes only receivespageChanged.This happens because both callbacks are routed through the same
TopChangeEvent/topChangeevent path, andTopChangeEventinherits React Native's default coalescing behavior.In practice, the later
pageChangedevent can replace the earlierloadCompleteevent for the same PDF view before JS receives it.We hit this in Expensify while validating local PDF receipts on Android:
Video demo
Video.Project.15.mov
Affected code
android/src/main/java/org/wonday/pdf/PdfView.javaonPageChanged(...)dispatchespageChanged|...throughTopChangeEventloadComplete(...)dispatchesloadComplete|...throughTopChangeEventandroid/src/main/java/org/wonday/pdf/events/TopChangeEvent.javatopChangecanCoalesce()fabric/RNPDFPdfNativeComponent.jsonChangeExpected behavior
If native emits both
loadCompleteandpageChanged, JS should receive both callbacks in order.Actual behavior
Native Android finishes loading the PDF, but JS can miss
onLoadCompleteentirely because the event was coalesced away before delivery.Why this matters
This breaks the
onLoadCompletecontract under Android/Fabric.In our Expensify case, it showed up during PDF validation: native rendered the PDF successfully, but JS never received
onLoadComplete, so the app treated the PDF as if validation had not completed.Root cause
loadCompleteandpageChangedare currently treated as the same coalescible event type for the same view:TopChangeEventtopChangeonChangecanCoalesce() == trueThat allows Fabric to keep only the later event.
Proposed fix
Make
TopChangeEventnon-coalescible on Android:This is the smallest fix that preserves the existing event model while ensuring both
loadCompleteandpageChangedreach JS.Alternative
A larger alternative would be to split these callbacks into distinct native/Fabric events instead of multiplexing them through shared
topChange, but that seems unnecessary for fixing the event loss itself.Validation
i confirmed the behavior locally with temporary native/JS tracing:
loadCompletepageChangedpageChangedloadCompletepageChangedThat points to event coalescing as the source of the loss.
Patch PR
#1011