Skip to content

Conversation

@andriivitiv
Copy link
Contributor

Details

Related Issues

Expensify/App#76303

Manual Tests

  1. Apply the following patch in E/App to make testing easier:

    Diff
    diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx
    index 0c48d7aa86d..9d79b61672a 100644
    --- a/src/components/PDFView/index.tsx
    +++ b/src/components/PDFView/index.tsx
    @@ -17,13 +17,16 @@ import variables from '@styles/variables';
     import {retrieveMaxCanvasArea, retrieveMaxCanvasHeight, retrieveMaxCanvasWidth} from '@userActions/CanvasSize';
     import CONST from '@src/CONST';
     import ONYXKEYS from '@src/ONYXKEYS';
    +import Button from '@components/Button';
    +import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL';
     import PDFPasswordForm from './PDFPasswordForm';
     import type {PDFViewProps} from './types';
     
     const LOADING_THUMBNAIL_HEIGHT = 250;
     const LOADING_THUMBNAIL_WIDTH = 250;
     
    -function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, style, isUsedAsChatAttachment, onLoadError}: PDFViewProps) {
    +function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL: sourceURLOrig, style, isUsedAsChatAttachment, onLoadError}: PDFViewProps) {
    +    const [sourceURL, setSourceURL] = useState(sourceURLOrig);
         const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
         const styles = useThemeStyles();
         const StyleUtils = useStyleUtils();
    @@ -97,6 +100,13 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, sty
                     style={outerContainerStyle}
                     tabIndex={0}
                 >
    +                <Button onPress={() => {
    +                    const source1 = '/staging/receipts/w_af0d78b7d3ef8de37b50415d9d1971fff6104da2.pdf';
    +                    const source2 = '/staging/chat-attachments/550067240287188185/w_41789e802d34503ca88c1088e9598f43b81e8378.pdf';
    +                    const nextSource = sourceURL?.includes(source1) ? source2 : source1;
    +                    const encryptedSource = addEncryptedAuthTokenToURL(nextSource);
    +                    setSourceURL(encryptedSource);
    +                }} text="Open different PDF" />
                     <PDFPreviewer
                         contentContainerStyle={style as CSSProperties}
                         file={sourceURL}
    @@ -106,7 +116,7 @@ function PDFView({onToggleKeyboard, fileName, onPress, isFocused, sourceURL, sty
                         maxCanvasHeight={maxCanvasHeight}
                         maxCanvasArea={maxCanvasArea}
                         LoadingComponent={
    -                        <LoadingIndicator
    +                        <View
                                 style={
                                     isUsedAsChatAttachment && [
                                         styles.chatItemPDFAttachmentLoading,

    You may also need to update the source URLs to PDFs uploaded from your account.

  2. Open a PDF viewer (attachment or receipt) and quickly click on the Open different PDF button multiple times.

  3. Observe that the page crashes and the following error is printed in the console: Cannot read properties of null (reading 'sendWithPromise').

  4. Run npm i && npm run build && npm pack on this PR to build the package.

  5. Install it in E/App using npm pkg set overrides.webpack=^5.104.1 devDependencies.webpack=^5.104.1 && npm install ../react-fast-pdf/react-fast-pdf-1.0.30.tgz (you may need to adjust the path), and remove esm/ from the path here.

  6. Repeat step 2 and verify that the page no longer crashes.

Linked PRs

Videos

Before After
Screen.Recording.2026-01-10.at.20.11.40.mov
Screen.Recording.2026-01-10.at.21.54.21.mov

@@ -1,4 +1,4 @@
import PDFPreviewer from './PDFPreviewer';
import PDFPreviewer from './PDFPreviewer.js';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was a bit skeptical of importing .js extensions directly in a .ts source, but this is actually the expected behavior for the current choice of TS configs:

compilerOptions: {
    module: "nodenext",
    moduleResolution: "nodenext",
}

This is a subtle distinction explained here.

We could support extensionless import by using moduleResolution: "bundler", but that would force that any consumer must use a bundler like webpack. Generally that would likely be fine, but there might be edge cases such as running the package in a Node.js server for SSR or running in a test runner. So it seems the current choice is actually the most portable, even if it feels a bit foreign. It's correct according to the official guidelines of TypeScript + Node.js

@roryabraham roryabraham merged commit 19a6117 into Expensify:main Jan 20, 2026
5 checks passed
@os-botify
Copy link
Contributor

os-botify bot commented Jan 20, 2026

🚀 Published to npm in 1.0.31 🎉

@andriivitiv andriivitiv deleted the 76303-upgrade-react-pdf-and-migrate-to-ESM branch January 20, 2026 16:48
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.

2 participants