Skip to content

App Crash with this.lastRNBFTask.cancel is not a function #796

Open

Description

@riteshshah5432

react-native version: 0.72.4

react-native-pdf version: 6.7.4

What platform does your issue occur on? : Both

Describe your issue as precisely as possible :

  1. When the PDF is loading and try to back to the screen that time app crashes
  2. logs
    TypeError: this.lastRNBFTask.cancel is not a function (it is undefined)

Screenshot
Simulator Screenshot - iPhone 15 Pro - 2023-12-23 at 18 09 22

Here is the code:
<Pdf
trustAllCerts={false}
source={{ uri: URL }}
onError={(error) => {
console.log(error);
}}
onLoadComplete={(numberOfPages, filePath) => {
console.log('completed')
}}
renderActivityIndicator={() => ()}
style={style.pdf} />

Activity

  1. Subiyel commented on Dec 27, 2023

    @Subiyel

    same issue

  2. mdalishanali commented on Dec 31, 2023

    @mdalishanali

    do you find anything?

  3. minh-dai commented on Jan 3, 2024

    @minh-dai

    +1

  4. kartavyaparekh96 commented on Jan 4, 2024

    @kartavyaparekh96

    +1

  5. alex-strae commented on Jan 5, 2024

    @alex-strae

    I have the same issue when browsing fast between different unloaded PDFs and download does not have time to finish before I start fetching new PDF.

    This is due to this code introduced and merged about 2 weeks ago. In index.js, line 279, with declaration of this.lastRNBFTask.

    BAD CODE:
    .catch(async (error) => {
    this._onError(error);
    });

    comment it out and problem solved.

    I am not an very experienced dev, but my guess is that the catch intercepts the cancel call that is supposed to be sent to ReactNativeBlobUtil and run its course there as it should, so instead it doesn't, and the code asks where the hell is my cancel method? Crash.

    Well we caught the error before the cancel could do it's thing...

    There is another similar catch on line 323. That one is fine, let it be.

  6. mdalishanali commented on Jan 5, 2024

    @mdalishanali
  7. alex-strae commented on Jan 5, 2024

    @alex-strae

    Well, what do you want to do? For me, I had to have the app stop crashing when browsing fast between PDFs. The better approach for me was to comment out the recently introduced code (that I specified in last post).

  8. mdalishanali commented on Jan 5, 2024

    @mdalishanali
  9. kartavyaparekh96 commented on Jan 5, 2024

    @kartavyaparekh96

    I have downgrade version @6.7.2 its working fine

  10. alex-strae commented on Jan 5, 2024

    @alex-strae
  11. AminDannak commented on Jan 10, 2024

    @AminDannak

    I have downgrade version @6.7.2 its working fine

    for me, it lead to a build issue. i used 6.7.3 and it seems fine. no crash

  12. shahid-0 commented on Jan 11, 2024

    @shahid-0

    I have the same issue when browsing fast between different unloaded PDFs and download does not have time to finish before I start fetching new PDF.

    This is due to this code introduced and merged about 2 weeks ago. In index.js, line 279, with declaration of this.lastRNBFTask.

    BAD CODE: .catch(async (error) => { this._onError(error); });

    comment it out and problem solved.

    I am not an very experienced dev, but my guess is that the catch intercepts the cancel call that is supposed to be sent to ReactNativeBlobUtil and run its course there as it should, so instead it doesn't, and the code asks where the hell is my cancel method? Crash.

    Well we caught the error before the cancel could do it's thing...

    There is another similar catch on line 323. That one is fine, let it be.

    I also have the same issue, when I press back button it arise the same error, then I saw your comment and try to do the same thing as you mentioned. But still it arise error. It didn't crash the app but arise error on the onError props, but I don't want to arise any error on the cancel. Then I commented out the below code on the line 146, and it works for me.

    // componentWillUnmount() {
    //     this._mounted = false;
    //     if (this.lastRNBFTask) {
    //         this.lastRNBFTask.cancel(err => {
    //         });
    //         this.lastRNBFTask = null;
    //     }
    // }
    
  13. abanobmikaeel commented on Jan 23, 2024

    @abanobmikaeel

    Also happening to me any way to solve? Happening on latest also. @shahid-0 how does one deploy with commented out code? Is that possible?

  14. alex-strae commented on Jan 23, 2024

    @alex-strae
  15. UgurGumushan commented on Jan 31, 2024

    @UgurGumushan

    +1
    This is a blocker and I can't update this package anymore

  16. 5 remaining items

  17. KarlosQ commented on Mar 7, 2024

    @KarlosQ

    @wonday Do you see a solution for this 6.7.4 regression?

  18. UgurGumushan commented on Mar 11, 2024

    @UgurGumushan

    I created this patch (patch-package) for avoid this crash:

    react-native-pdf+6.7.4.patch

    diff --git a/node_modules/react-native-pdf/index.js b/node_modules/react-native-pdf/index.js
    index c7c58d8..70a6057 100644
    --- a/node_modules/react-native-pdf/index.js
    +++ b/node_modules/react-native-pdf/index.js
    @@ -127,7 +127,7 @@ export default class Pdf extends Component {
     
             if ((nextSource.uri !== curSource.uri)) {
                 // if has download task, then cancel it.
    -            if (this.lastRNBFTask) {
    +            if (this.lastRNBFTask?.cancel) {
                     this.lastRNBFTask.cancel(err => {
                         this._loadFromSource(this.props.source);
                     });
    @@ -145,7 +145,7 @@ export default class Pdf extends Component {
     
         componentWillUnmount() {
             this._mounted = false;
    -        if (this.lastRNBFTask) {
    +        if (this.lastRNBFTask?.cancel) {
                 this.lastRNBFTask.cancel(err => {
                 });
                 this.lastRNBFTask = null;
    @@ -250,7 +250,7 @@ export default class Pdf extends Component {
     
         _downloadFile = async (source, cacheFile) => {
     
    -        if (this.lastRNBFTask) {
    +        if (this.lastRNBFTask?.cancel) {
                 this.lastRNBFTask.cancel(err => {
                 });
                 this.lastRNBFTask = null;
    

    Would you like to create a pull request with those changes?

  19. kamiltenkai commented on Mar 12, 2024

    @kamiltenkai

    Hey. Adding "cache: true" to source helped me. Like this:
    <Pdf source={{ uri: url, cache: true }}

  20. nawazokz commented on Mar 15, 2024

    @nawazokz

    I have the same issue when browsing fast between different unloaded PDFs and download does not have time to finish before I start fetching new PDF.
    This is due to this code introduced and merged about 2 weeks ago. In index.js, line 279, with declaration of this.lastRNBFTask.
    BAD CODE: .catch(async (error) => { this._onError(error); });
    comment it out and problem solved.
    I am not an very experienced dev, but my guess is that the catch intercepts the cancel call that is supposed to be sent to ReactNativeBlobUtil and run its course there as it should, so instead it doesn't, and the code asks where the hell is my cancel method? Crash.
    Well we caught the error before the cancel could do it's thing...
    There is another similar catch on line 323. That one is fine, let it be.

    I also have the same issue, when I press back button it arise the same error, then I saw your comment and try to do the same thing as you mentioned. But still it arise error. It didn't crash the app but arise error on the onError props, but I don't want to arise any error on the cancel. Then I commented out the below code on the line 146, and it works for me.

    // componentWillUnmount() {
    //     this._mounted = false;
    //     if (this.lastRNBFTask) {
    //         this.lastRNBFTask.cancel(err => {
    //         });
    //         this.lastRNBFTask = null;
    //     }
    // }
    

    thanks bro, that's work for me

  21. dariyd commented on Mar 26, 2024

    @dariyd

    I created this patch (patch-package) for avoid this crash:

    react-native-pdf+6.7.4.patch

    diff --git a/node_modules/react-native-pdf/index.js b/node_modules/react-native-pdf/index.js
    index c7c58d8..70a6057 100644
    --- a/node_modules/react-native-pdf/index.js
    +++ b/node_modules/react-native-pdf/index.js
    @@ -127,7 +127,7 @@ export default class Pdf extends Component {
     
             if ((nextSource.uri !== curSource.uri)) {
                 // if has download task, then cancel it.
    -            if (this.lastRNBFTask) {
    +            if (this.lastRNBFTask?.cancel) {
                     this.lastRNBFTask.cancel(err => {
                         this._loadFromSource(this.props.source);
                     });
    @@ -145,7 +145,7 @@ export default class Pdf extends Component {
     
         componentWillUnmount() {
             this._mounted = false;
    -        if (this.lastRNBFTask) {
    +        if (this.lastRNBFTask?.cancel) {
                 this.lastRNBFTask.cancel(err => {
                 });
                 this.lastRNBFTask = null;
    @@ -250,7 +250,7 @@ export default class Pdf extends Component {
     
         _downloadFile = async (source, cacheFile) => {
     
    -        if (this.lastRNBFTask) {
    +        if (this.lastRNBFTask?.cancel) {
                 this.lastRNBFTask.cancel(err => {
                 });
                 this.lastRNBFTask = null;
    

    This worked for me. Many thanks!

  22. wonday commented on Mar 26, 2024

    @wonday
    Owner

    Would you like to create a pull request with those changes?

  23. abanobmikaeel commented on Apr 9, 2024

    @abanobmikaeel

    Isn't that code kinda overwriting needed functionality? @wonday

    As @elkinjosetm mentioned:

    Even-though the patch prevents the app from crashing, it's not actually fixing the problem, because the cancel is not triggered so the download keeps going in background, which is not ideal.

  24. chandwho commented on Apr 23, 2024

    @chandwho

    I was facing the same issue. Updating to the latest version 6.7.5 has fixed it.

  25. alex-strae commented on May 13, 2024

    @alex-strae

    I updated to 6.7.5 recently (RN 0.74.1, Android: Fabric Disabled, Bridgeless Disabled, TurboModules enabled | iOS new architecture disabled) and the problem was back. Crash when quickly changing source for PdfViewer (this.lastRNBFTask.cancel is not a function) by scrolling between different "dates" with different pdf sources.

    I applied Karlos patch. While it does work, if I scroll back to a date I just skipped, it's noticeable that there was no cancel fired. The PDF was loaded in the background even though I switched to another date.

    The reason for this should be that the error manifested itself if this.lastRNBFTask.cancel was undefined, as in it was falsy. This means the patch will make the function never run if it doesn't exist. While that is good, wouldn't it be better if the function existed for us and we could run it? Yes.

    I tried my old patch I posted a while ago which is simple: comment out catch-code on line 279, after the creation of the lastRNBFTask object creation.

    /*
    .catch(async (error) => {
    this._onError(error);
    });
    */

    And now everything works as intended (I think?). The reasons it works, I believe, is that when we reset the pdf source fast, there is an error in the creation of the lastRNBFTask object. This error get's caught in the catch and the lastRNBFTask object gets corrupted, or at least interrupted in its creation. There is no cancel method available, and that causes the crash (.cancel is undefined).
    By disabling the catch, the lastRNBFTask object can get created with all it's helper methods (such as the .cancel) even though another process before it such as the .progress or .fetch had an error.

  26. iduke-rbi commented on May 23, 2024

    @iduke-rbi

    The above suggestion from @alex-strae worked for me with Expo 50 and 6.7.5.

    I was getting a crash with stacktrace TypeError: undefined is not a function at componentDidUpdate. This no longer crashes with the catch block commented out.

  27. roniemartinez commented on Jun 6, 2024

    @roniemartinez

    This happens to us when pressing back button on Stack (expo-router) while PDF is not completely loaded. While not a proper solution, we have made a workaround by preventing back until PDF is loaded (https://reactnavigation.org/docs/preventing-going-back/). Looking forward to the proper fix.

  28. tsalama commented on Jun 6, 2024

    @tsalama

    This happens to us when pressing back button on Stack (expo-router) while PDF is not completely loaded. While not a proper solution, we have made a workaround by preventing back until PDF is loaded (https://reactnavigation.org/docs/preventing-going-back/). Looking forward to the proper fix.

    Same issue for us!

  29. jaiieth commented on Jun 20, 2024

    @jaiieth

    I believe the issue occurs because this.lastRNBFTask is unintentionally assigned a Promise object on line 262:

    this.lastRNBFTask = ReactNativeBlobUtil.config({
        // response data will be saved to this path if it has access rights.
        path: tempCacheFile,
        trusty: this.props.trustAllCerts,
    })
        .fetch(
            source.method ? source.method : 'GET',
            source.uri,
            source.headers ? source.headers : {},
            source.body ? source.body : ""
        )
        // listen to the download progress event
        .progress((received, total) => {
            if (this.props.onLoadProgress) {
                this.props.onLoadProgress(received / total);
            }
            if (this._mounted) {
                this.setState({ progress: received / total });
            }
        })
        .catch(async (error) => { // <- remove this
            this._onError(error);
        });
    

    The issue arises because ReactNativeBlobUtil.fetch() returns an extended Promise object, which includes .cancel() method. However, calling .catch() on this extended Promise returns a normal Promise, which does not have the .cancel() method. This results in an error when attempting to call this.lastRNBFTask.cancel().

    Removing the .catch() as suggested by @alex-strae should resolve the crash.

    Additionally, when calling .cancel(), ReactNativeBlobUtil will reject the Promise, triggering the onError method.

    To handle this properly, modify line 321 as follows:

    .catch(async (error) => {
        this._unlinkFile(tempCacheFile);
        this._unlinkFile(cacheFile);
        if (error.name !== 'ReactNativeBlobUtilCanceledFetch') {
            this._onError(error);
        }
    });
    

    This change ensures that the onError method is only called for errors other than ReactNativeBlobUtilCanceledFetch.

  30. Maniae commented on Oct 10, 2024

    @Maniae

    #796 (comment) from @jaiieth is the solution that makes the most sense, and actually solves the issue 👍

    This is quite strange #827 got merged as a "fix" as it only handles unMount crash situations, creates dead code in comments, and does not in fact fixes the issues of changing the pdf source url 😕.

    react-native-pdf+6.7.5.patch

    diff --git a/node_modules/react-native-pdf/index.js b/node_modules/react-native-pdf/index.js
    index b81a141..2b65ee0 100644
    --- a/node_modules/react-native-pdf/index.js
    +++ b/node_modules/react-native-pdf/index.js
    @@ -276,9 +276,6 @@ export default class Pdf extends Component {
                     if (this._mounted) {
                         this.setState({progress: received / total});
                     }
    -            })
    -            .catch(async (error) => {
    -                this._onError(error);
                 });
     
             this.lastRNBFTask
    @@ -323,7 +320,9 @@ export default class Pdf extends Component {
                 .catch(async (error) => {
                     this._unlinkFile(tempCacheFile);
                     this._unlinkFile(cacheFile);
    -                this._onError(error);
    +                if (error.name !== 'ReactNativeBlobUtilCanceledFetch') {
    +                    this._onError(error);
    +                }            
                 });
     
         };
  31. acuD1 commented on Jan 1, 2025

    @acuD1

    I did tried a different approach.

        _downloadFile = async (source, cacheFile) => {
          console.log('Download file');
    
          // 1. Cancel any existing fetch-task
          if (this.lastRNBFTask && typeof this.lastRNBFTask.cancel === 'function') {
            console.log('Cancel last task');
            this.lastRNBFTask.cancel(() => {
              console.log('Last task canceled');
            });
            this.lastRNBFTask = null;
          }
    
          // 2. Prepare the temp file path and ensure any leftover file is removed
          const tempCacheFile = cacheFile + '.tmp';
          await this._unlinkFile(tempCacheFile);
    
          // 3. Create the actual fetch-task object
          const task = ReactNativeBlobUtil.config({
            path: tempCacheFile,
            trusty: this.props.trustAllCerts,
          }).fetch(
            source.method ?? 'GET',
            source.uri,
            source.headers ?? {},
            source.body ?? ''
          );
    
          // 4. Assign the fetch-task object to this.lastRNBFTask
          this.lastRNBFTask = task;
    
          // 5. Now chain the progress, then, and catch
          task
            .progress((received, total) => {
              this.props.onLoadProgress?.(received / total);
              if (this._mounted) {
                this.setState({ progress: received / total });
              }
            })
            .then(async (res) => {
              // Clear the reference to the task
              this.lastRNBFTask = null;
    
              // Validate the file size if headers include Content-Length (to detect partial downloads)
              if (
                res?.respInfo?.headers &&
                !res.respInfo.headers['Content-Encoding'] &&
                !res.respInfo.headers['Transfer-Encoding'] &&
                res.respInfo.headers['Content-Length']
              ) {
                const expectedContentLength = res.respInfo.headers['Content-Length'];
                let actualContentLength;
    
                try {
                  const fileStats = await ReactNativeBlobUtil.fs.stat(res.path());
                  if (!fileStats || !fileStats.size) {
                    throw new Error('FileNotFound:' + source.uri);
                  }
                  actualContentLength = fileStats.size;
                } catch (error) {
                  throw new Error('DownloadFailed:' + source.uri);
                }
    
                if (expectedContentLength != actualContentLength) {
                  throw new Error('DownloadFailed:' + source.uri);
                }
              }
    
              // Move the downloaded file from temp to the final location
              await this._unlinkFile(cacheFile);
              await ReactNativeBlobUtil.fs.cp(tempCacheFile, cacheFile);
    
              if (this._mounted) {
                this.setState({
                  path: cacheFile,
                  isDownloaded: true,
                  progress: 1,
                });
              }
    
              // Clean up the temp file
              await this._unlinkFile(tempCacheFile);
            })
            .catch(async (error) => {
              console.log('Download error:', error);
    
              // Clean up temp files
              await this._unlinkFile(tempCacheFile);
              await this._unlinkFile(cacheFile);
              this._onError(error);
    
              // // If it wasn't a user-cancel, call the error callback
              // if (error.name !== 'ReactNativeBlobUtilCanceledFetch') {
              //   this._onError(error);
              // }
            });
        };

    This resolve completely the undefined object, as we store the fetch-task immediate after fetching it. This ensures it’s the actual task object that has the cancel() method, not a Promise.

    I can now properly what it's happening afterward. A SIGSEGV from FD_Done_Face.

    OS Version: Android 14 (UP1A.231005.007.A528BXXSAGXK1)
    Report Version: 104
    
    Exception Type: Unknown (SIGSEGV)
    
    Application Specific Information:
    Segfault
    
    Thread 0 Crashed:
    0   base.apk                        0x715ea23b84        FT_Done_Face
    1   base.apk                        0x715e9cf05c        CFX_Face::~CFX_Face
    2   base.apk                        0x715e9cf088        CFX_Face::~CFX_Face
    3   base.apk                        0x715e9d58c0        CFX_GlyphCache::~CFX_GlyphCache
    4   base.apk                        0x715e9d58ec        CFX_GlyphCache::~CFX_GlyphCache
    5   base.apk                        0x715e9d0684        CFX_Font::~CFX_Font
    6   base.apk                        0x715eac0f90        CPDF_Font::~CPDF_Font
    7   base.apk                        0x715eac5b68        CPDF_Type1Font::~CPDF_Type1Font
    8   base.apk                        0x715e9735a8        CPDF_TextState::TextData::~TextData
    9   base.apk                        0x715e95c6e8        CPDF_GraphicStates::~CPDF_GraphicStates
    10  base.apk                        0x715e9721b8        CPDF_TextObject::~CPDF_TextObject
    11  base.apk                        0x715e964f18        <unknown> + 486918213400
    12  base.apk                        0x715e964558        <unknown> + 486918210904
    13  base.apk                        0x715e9644f0        CPDF_PageObjectHolder::~CPDF_PageObjectHolder
    14  base.apk                        0x715e9616bc        CPDF_Page::~CPDF_Page
    15  base.apk                        0x71b379d984        Java_io_legere_pdfiumandroid_PdfPage_nativeClosePage
    16  libart.so                       0x723b00d370        <unknown> + 490616181616
    17  libart.so                       0x723aff6b74        <unknown> + 490616089460
    18  libart.so                       0x723aff01e4        <unknown> + 490616062436
    19  libart.so                       0x723b3f7170        <unknown> + 490620285296
    20  libart.so                       0x723b00f9d8        <unknown> + 490616191448
    21  <unknown>                       0x71c6f6316c        <unknown>
    22  libart.so                       0x723afe2650        <unknown> + 490616006224
    23  libart.so                       0x723aff0a78        <unknown> + 490616064632
    24  libart.so                       0x723b3f7170        <unknown> + 490620285296
    25  libart.so                       0x723b00f9d8        <unknown> + 490616191448
    26  <unknown>                       0x71c704d6d0        <unknown>
    27  libart.so                       0x723afe2650        <unknown> + 490616006224
    28  libart.so                       0x723aff0a78        <unknown> + 490616064632
    29  libart.so                       0x723b3f7170        <unknown> + 490620285296
    30  libart.so                       0x723b00f9d8        <unknown> + 490616191448
    31  <unknown>                       0x71c6f65060        <unknown>
    32  libart.so                       0x723afe2650        <unknown> + 490616006224
    33  libart.so                       0x723afe3c9c        <unknown> + 490616011932
    34  libart.so                       0x723afdcde0        <unknown> + 490615983584
    35  libart.so                       0x723b3f7264        <unknown> + 490620285540
    36  libart.so                       0x723b00f9d8        <unknown> + 490616191448
    37  <unknown>                       0x71c90f0378        <unknown>
    38  libart.so                       0x723afe2650        <unknown> + 490616006224
    39  libart.so                       0x723aff0a78        <unknown> + 490616064632
    40  libart.so                       0x723b3f7170        <unknown> + 490620285296
    41  libart.so                       0x723b00f9d8        <unknown> + 490616191448
    42  <unknown>                       0x71c90f054c        <unknown>
    43  libart.so                       0x723afe2650        <unknown> + 490616006224
    44  libart.so                       0x723aff0a78        <unknown> + 490616064632
    45  libart.so                       0x723b3f7170        <unknown> + 490620285296
    46  libart.so                       0x723b00f9d8        <unknown> + 490616191448
    47  <unknown>                       0x71c90f0768        <unknown>
    48  libart.so                       0x723afe19dc        <unknown> + 490616003036
    49  libart.so                       0x723b00d498        <unknown> + 490616181912
    50  <unknown>                       0x9d6f68d0          <unknown>

    From now on, I don't have the skills to investigate further. But clearly something nasty is going on, and probably on react-native-blob-util side.

    image

    I am using:

      "dependencies": {
        "@expo/metro-runtime": "^4.0.0",
        "@sentry/react-native": "~6.3.0",
        "axios": "^1.7.5",
        "expo": "~52.0.9",
        "expo-auth-session": "^6.0.1",
        "expo-constants": "~17.0.3",
        "expo-dev-client": "~5.0.3",
        "expo-font": "~13.0.1",
        "expo-image": "~2.0.1",
        "expo-linking": "~7.0.3",
        "expo-localization": "~16.0.0",
        "expo-router": "~4.0.7",
        "expo-splash-screen": "~0.29.12",
        "expo-status-bar": "~2.0.0",
        "expo-system-ui": "~4.0.3",
        "expo-web-browser": "^14.0.1",
        "react": "18.3.1",
        "react-dom": "18.3.1",
        "react-native": "0.76.5",
        "react-native-blob-util": "^0.21.2",
        "react-native-pdf": "^6.7.6",
        "react-query-kit": "^3.3.0",
      },

    No crash on IOS Simulator (18.0)

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions