Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

TypeError: Cannot read property 'createErrorBoundary' of undefined #1112

Closed
duzliang opened this issue Oct 23, 2020 · 24 comments
Closed

TypeError: Cannot read property 'createErrorBoundary' of undefined #1112

duzliang opened this issue Oct 23, 2020 · 24 comments
Labels
bug Confirmed bug released This feature/bug fix has been released

Comments

@duzliang
Copy link

Following the Bugsnag Docs to config the project and run got this error from:
const ErrorBoundary = Bugsnag.getPlugin('react').createErrorBoundary(React);

  • react 16.11.0
  • react-native 0.62.2
  • @bugsnag/react-native 7.5.0
@johnkiely1
Copy link
Member

Hi @duzliang ,

Could you confirm what error you are seeing? Also could you share you Bugsnag configuration code for both your JS and native layers. Feel free to email [email protected] if you don't want to share this information publicly.

Thanks

@johnkiely1 johnkiely1 added the awaiting feedback Awaiting a response from a customer. Will be automatically closed after approximately 2 weeks. label Oct 23, 2020
@duzliang
Copy link
Author

@johnkiely1 Thank you for viewing this issue:

Both the Android and iOS configuration are set up step by step by the Docs

Below is a simple Javascript code snippets:

  1. App.js
import Bugsnag from '@bugsnag/react-native';

import ErrorView from './containers/ErrorView';

const ErrorBoundary = Bugsnag.getPlugin('react').createErrorBoundary(React);

export default class AppRepair extends React.Component {
  constructor(props) {
    Bugsnag.start();
  }

 render() {
    return (
      <ErrorBoundary FallbackComponent={ErrorView}>
        <Provider store={store}>
          <Routes handleExit={this.handleExit} />
        </Provider>
      </ErrorBoundary>
    );
  }
}
  1. index.js
import { AppRegistry } from 'react-native';
import App from './src/App.js';
import { name as appName } from './app.json';

AppRegistry.registerComponent(appName, () => App);

when running the App, get the error:

TypeError: Cannot read property 'createErrorBoundary' of undefined 

@kennethjiang
Copy link

I'm having the same problem here.

  • react 16.13.1
  • react-native 0.63.2
  • @bugsnag/react-native 7.5.1

@mattdyoung
Copy link
Contributor

@duzliang
The issue is that you're calling Bugsnag.getPlugin('react') before you call Bugsnag.start().

Bugsnag.start() must be called before any other method so you'll need to move it out of the constructor of your component.

@duzliang
Copy link
Author

duzliang commented Oct 27, 2020

Move it out of the constructor also get the Error while open the Debug mode of React Native Debug Menu
When close the Debug mode this error disappear

import Bugsnag from '@bugsnag/react-native';

import ErrorView from './containers/ErrorView';

Bugsnag.start();

const ErrorBoundary = Bugsnag.getPlugin('react').createErrorBoundary(React);

export default class AppRepair extends React.Component {
  constructor(props) {
  }

 render() {
    return (
      <ErrorBoundary FallbackComponent={ErrorView}>
        <Provider store={store}>
          <Routes handleExit={this.handleExit} />
        </Provider>
      </ErrorBoundary>
    );
  }
}

@mattdyoung
Copy link
Contributor

That does look like a bug, although one that would only have an impact when the debugger is attached.

We'll look into whether this can be resolved in a future release.

@mattdyoung mattdyoung added backlog We hope to fix this feature/bug in the future bug Confirmed bug and removed awaiting feedback Awaiting a response from a customer. Will be automatically closed after approximately 2 weeks. labels Oct 28, 2020
@adkenyon
Copy link

adkenyon commented Nov 5, 2020

Is there a workaround for this? Being unable to attach a debugger makes getPlugin unusable.

I'm using the reactNavigation, being core app functionality I'm not sure there's a way to lazily load this.

Any recommendations on a fix? happy to help if I can get pointed in the right direction.

@xljones
Copy link
Contributor

xljones commented Nov 6, 2020

Hey @adkenyon, we're looking into a fix for this notifier side. But, in the meantime you can guard against this by checking whether the getPlugin() call returns undefined, and in these cases point your application to a no-op ErrorBoundary, for example:

class NoopErrorBoundary extends React.Component {
    // define your Noop Error Boundary here
}

var ErrorBoundary;
if (typeof Bugsnag.getPlugin('react') === 'undefined') {
    ErrorBoundary = NoopErrorBoundary;
} else {
    ErrorBoundary = Bugsnag.getPlugin('react').createErrorBoundary(React);
}

// use the ErrorBoundary as normal

When running without the debugger, Bugsnag will continue as normal with the Bugsnag error boundary.

@adkenyon
Copy link

adkenyon commented Nov 6, 2020

@xander-jones thank you for the workaround!

@bugsnagbot bugsnagbot added scheduled Work is starting on this feature/bug and removed backlog We hope to fix this feature/bug in the future labels Jan 7, 2021
@wildseansy
Copy link

wildseansy commented Jan 14, 2021

Can you please reference this in your documentation until this is released?
I just spend 2-3 hours thinking I setup my app was wrong, meticulously looking at the docs, but it looks like this feature just doesn't work at all with recent versions of react-native and bugsnag, even with the base case in the documentation.

@yousif-bugsnag
Copy link
Contributor

Fixed in v7.6.1

@yousif-bugsnag yousif-bugsnag added released This feature/bug fix has been released and removed scheduled Work is starting on this feature/bug labels Jan 29, 2021
@interhub
Copy link

What i can do with code push if i need call asyncronus

const data = await codePush.getUpdateMetadata();
  const codePushVersLabel = `${data?.label}-${getVersion()}`;
  Bugsnag.start({
    codeBundleId: codePushVersLabel, 
    onError,
  });

const ErrorBoundary = Bugsnag.getPlugin('react').createErrorBoundary(React);

and

<ErrorBoundary
      onError={onError}
      FallbackComponent={ErrorFullscreenComponent}>
      {props.children}
    </ErrorBoundary>

Bugsnag is crashing and i have to add second block Bugsnag.start call

Bugsnag.start({
  onError,
});

@xljones
Copy link
Contributor

xljones commented Aug 19, 2021

👋 Hey @interhub, you should start Bugsnag synchronously as soon as you can in your application to ensure that you're capturing all errors. This also means that you'll be able to create your ErrorBoundary as soon as possible too, which I suspect would alleviate the need to start Bugsnag outside of your async await method. A second call to Bugsnag.start(), if Bugsnag is already running, would be ignored, you'd see the following in your console:

[bugsnag] Bugsnag.start() was called more than once. Ignoring.

I appreciate that you're looking to get the CodePush label async on the launch of the application to set the codeBundleId, however, this codeBundleId should be hardcoded into your JS bundle as it's needed at launch immediately. You may wish to inject this codeBundleId value at bundle build time as with CodePush, you'll always know what the next label is going to be as it's always going to be vX+1 where vX is the current label.

If you're seeing Bugsnag crash though, please do let us know with a new issue on this repo with details of the crash & what's happening to cause the crash, and we'll take a look into this! :)

@henrymoulton
Copy link

you should start Bugsnag synchronously as soon as you can in your application to ensure that you're capturing all errors.

appreciate that you're looking to get the CodePush label async on the launch of the application to set the codeBundleId, however, this codeBundleId should be hardcoded into your JS bundle as it's needed at launch immediately. You may wish to inject this codeBundleId value at bundle build time as with CodePush, you'll always know what the next label is going to be as it's always going to be vX+1 where vX is the current label.

@xander-jones I think this is related to an issue I think a lot of user's might have which is that while starting Bugsnag should be synchronous, CodePush users want to specify the codeBundleId when starting Bugsnag which is asynchronous:
await CodePush.getUpdateMetadata(CodePush.UpdateState.RUNNING)

Perhaps you could add some docs on how to "inject this codeBundleId value at bundle build time" OR specify in the docs https://docs.bugsnag.com/platforms/react-native/react-native/codepush/ that users should figure out how to do this themselves.

Should I start a separate issue on this topic? I feel a few Bugsnag + CodePush users might have this issue as I've had this on multiple apps.

@xljones
Copy link
Contributor

xljones commented Oct 12, 2021

Hey @henrymoulton, sure; it's difficult to specify an exact method that you should use as people use all sort of different build pipelines, and architect their apps in different ways. However, hopefully the following information will be hopefully be starting point for you? The concept here is to prep the source to have a codeBundleId equal to the binary version and the future CodePush label, if it's a CodePush update, else it'll just be set to the binary version

First, in index.js, I've added the following to grab version and codePushLabel from the package.json:

import Bugsnag from '@bugsnag/react-native';

const version = require("./package.json").version
const codePushLabel = require('./package.json').codePushLabel

Bugsnag.start({
  codeBundleId: version + (codePushLabel == null ? '' : '_' + codePushLabel)
});

Now, in package.json I've put a placeholder codePushLabel key, with a value of null:

{
  "name": "CodePushHarness",
  "version": "1.0.0",
  "codePushLabel": null,
// ... 

Next, we need to manipulate that codePushLabel value before we make a build. In this case, if releasing a binary, it should be set to null, else it should be set to the label which CodePush is going to set in future when you release.

This is where things diverge slightly depending on how you're building, but the following is a Node.js example I've put together, you can call this using node codepush.js STAGE BINARYVERSION.

It will:

  • Make a call to AppCenter to work out what the current label is for the stage provided
  • Increment that label by one and write it to the package.json's codePushLabel key, or set it to v1 if no CodePush release has happened yet.
  • Run the CodePush release (releaseCodepushUpdate())

⚠️ This is example code, and I'd highly recommend testing this before pushing anything live to production! Whilst I've tested this on a simple application, it doesn't take into account if you're running multiple parallel binary versions with CodePush updates on each.

const exec = require('child_process').exec
const fs = require('fs')

if (process.argv.length < 4) {
    console.error("Missing input args. Usage: node codepush.js STAGE BINARYVERSION")
    process.exit(1)
}

// Which stage are you deploying to?
const stage = process.argv[2]
console.debug("stage", stage)

// Which binary version is being targeted?
const binaryVersion = process.argv[3]
console.debug("binaryVersion", binaryVersion)

function injectLabelToPackage(label) {
    const pjsonFilename = "./package.json"
    const pjsonHandle = require(pjsonFilename)
    pjsonHandle.codePushLabel = label
    fs.writeFile(pjsonFilename, JSON.stringify(pjsonHandle, null, 2), function writeJSON(err) {
        if (err) {
            console.error(err)
            process.exit()
        } else {
            return console.debug(`Set codePushLabel to ${label} in ${pjsonFilename}`)
        }
    })
}

function releaseCodepushUpdate() {
    exec(`appcenter codepush release-react -d ${stage} -t ${binaryVersion} --output-dir build`, function(error, stdout, stderr){
        console.log(stdout)
        console.error(stderr)
    })
}

exec("appcenter codepush deployment list --output json", function(error, stdout, stderr){
    var codePushDeploymentList = JSON.parse(stdout)
    var thisDeployment = codePushDeploymentList.find(obj => {
        return obj.name == stage
    })

    console.debug("codePushDeploymentList", codePushDeploymentList)
    console.debug("thisDeployment", thisDeployment)

    if (thisDeployment !== undefined) {
        var newLabel = null

        if (thisDeployment.latestRelease.label !== undefined) {
            // a CodePush label already exists for this stage
            const currentLabel = thisDeployment.latestRelease.label
            console.debug("currentLabel", currentLabel)
            newLabel = "v" + (parseInt(currentLabel.substring(1, currentLabel.length)) + 1)
        } else {
            // no CodePush releases have been made yet for this stage, so this will be v1
            newLabel = "v1"
        }
    
        console.debug("newLabel", newLabel)
        injectLabelToPackage(newLabel)
    
        // execute your CodePush release here.
        releaseCodepushUpdate()
    } else {
        console.error(`Stage '${stage}' doesn't exist`)
        process.exit(1)
    }
})

In our docs we do mention that the codeBundleId does not need to match the label at all and that it's up to you to decide what you want this value to be, but it does need to be incremented for each release. It's purely just an identifier for matching source maps and can be anything, a semver, or a commit SHA etc.: https://docs.bugsnag.com/platforms/react-native/react-native/codepush/#setting-a-code-bundle-id

@henrymoulton
Copy link

Wow @xander-jones thanks so much for taking the time.

This looks like a snazzy script that I could definitely adapt for our needs.

@neb-b
Copy link

neb-b commented Apr 13, 2022

Is there any information on why/when Bugsnag.getPlugin('react') would return undefined? We recently had a production crash because of this. It seems there was some additional error/issue that happened before this (outside of bugsnag) that may have caused bugsnag to fail but I am still trying to find out what those were.

in bugsnag-plugin-react.d.ts I see that it can return undefined, but I'm having trouble tracking down when that would happen

declare module '@bugsnag/core' {
  interface Client {
    getPlugin(id: 'react'): BugsnagPluginReactResult | undefined
  }
}

https://github.com/bugsnag/bugsnag-js/blob/next/packages/plugin-react/types/bugsnag-plugin-react.d.ts#L25-L29

My configuration is:

import Bugsnag from '@bugsnag/js'
import BugsnagPluginReact from '@bugsnag/plugin-react'
...


Bugsnag.start({
  apiKey: process.env.NEXT_PUBLIC_BUGSNAG_API_KEY,
  appVersion: process.env.NEXT_PUBLIC_COMMIT_HASH,
  enabledReleaseStages: ['development'],
  plugins: [new BugsnagPluginReact()],
})

const BugsnagErrorBoundary = Bugsnag.getPlugin('react').createErrorBoundary(React)

@luke-belton
Copy link
Member

Hi @neb-b - Bugsnag.getPlugin('react') would only return undefined if BugsnagPluginReact is not passed into the Bugsnag.start() plugins property, or if Bugsnag.getPlugin('react') is called before Bugsnag.start().

Is it possible that one of these conditions were met when you saw your production crash (e.g. if Bugsnag.start was wrapped inside some condition that prevented it from being called prior to getting the plugin)?

@neb-b
Copy link

neb-b commented Apr 22, 2022

@luke-belton thanks for the info. Based on that, there must have been issues with the app reading the bugsnag api key, so I'm guessing it was undefined.

@neb-b
Copy link

neb-b commented Apr 25, 2022

@luke-belton upon further investigation, it doesn't appear that the app would have issues reading the api key so everything was passed in correctly. Bugsnag.start also wasn't wrapped in any conditional that would have prevented it from not being called.

Our issue started from netlify network request errors, but our error logs were filled with TypeError: Cannot read property 'createErrorBoundary' of undefined at the same time, so it seems to be there could be some other case where Bugsnag.getPlugin('react') could start returning undefined (albeit not under normal circumstances)

@yousif-bugsnag
Copy link
Contributor

Hi @neb-b, we're not aware of any other case in which the call to Bugsnag.getPlugin('react') would return undefined. If the configuration is as above (with Bugsnag.start() being called first, passing in BugsnagPluginReact) we would always expect this to return the initialised plugin. However if you're able to provide a reproduction case we'd be more than happy to investigate further.

@neb-b
Copy link

neb-b commented Apr 29, 2022

@yousif-bugsnag it appears there were a lot of network errors from netlify around that time. Could it be possible that the POST request to session.bugsnag.com failed in a way that would lead to Bugsnag.getPlugin('react') returning undefined? Could you share where that code is so I can try to reproduce? I am having issues finding it

@yousif-bugsnag
Copy link
Contributor

Hi @neb-b, failed network requests to sessions.bugnsag.com would not cause Bugsnag.getPlugin() to return undefined. This would only happen in the scenarios described above.

Could you share where that code is so I can try to reproduce? I am having issues finding it

Here is the getPlugin() implementation in the client in case it helps debugging your issue:

getPlugin (name) {
return this._plugins[`~${name}~`]
}

And the React plugin lives here: https://github.com/bugsnag/bugsnag-js/tree/master/packages/plugin-react

@parkwherever
Copy link

Since nextjs cannot render on the build step, does this mean you can't use the ErrorBoundary there?

https://github.com/bugsnag/bugsnag-js/blob/next/examples/js/nextjs/lib/bugsnag.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Confirmed bug released This feature/bug fix has been released
Projects
None yet
Development

No branches or pull requests