Skip to content

Instantly share code, notes, and snippets.

@Psykar
Forked from Aldredcz/renderErrorHandling.md
Created September 26, 2016 01:38
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Psykar/d01f6e6e9575926768123ff7af82fd11 to your computer and use it in GitHub Desktop.
Save Psykar/d01f6e6e9575926768123ff7af82fd11 to your computer and use it in GitHub Desktop.

Notes

  • This code handles any JS runtime error during rendering React components. Without this handling, once an error occurs, whole component tree is damaged and can't be used at all. With this handling, nothing will be rendered in production environment (error span in dev env.) + in production the error is logged to Sentry (if you are not using it just delete related code)
  • This is basicaly a workaround for proposed feature in React core - described in Issue: facebook/react#2461
  • Works for all variants of Component creation - React.createClass, extending React.Component and also stateless functional components.
  • To get this work, just put this snippet into your entry js file. Then it will work in whole application.
  • Also supporting React Hot Reload!
  • If you find this useful, please retweet https://twitter.com/Aldredcz/status/744650159942995968 :)

Ideas

  • specify custom error renderer (global / per component, e.g. by implementing method renderOnError() in a component)

Snippet code

import React from 'react'

let errorPlaceholder = <noscript/>

if (__DEVELOPMENT__) {
  errorPlaceholder = (
    <span
      style={{
        background: 'red',
        color: 'white',
      }}
    >
            Render error!
        </span>
  )
  console.debug('Error boundaries monkey patched') // eslint-disable-line
}

function logError(Component, error) {
  const errorMsg = `Error while rendering component. Check render() method of component '${Component.displayName || Component.name || '[unidentified]'}'.`

  console.error(errorMsg, 'Error details:', error); // eslint-disable-line
}

function monkeypatchRender(prototype) {
  if (prototype && prototype.render && !prototype.render.__handlingErrors) {
    const originalRender = prototype.render

    prototype.render = function monkeypatchedRender() {
      try {
        return originalRender.call(this)
      } catch (error) {
        logError(prototype.constructor, error)

        return errorPlaceholder
      }
    }

    prototype.render.__handlingErrors = true // flag render method so it's not wrapped multiple times
  }
}

const originalCreateElement = React.createElement
const statelessMap = new Map()
React.createElement = (Component, ...rest) => {
  if (typeof Component === 'function') {

    if (typeof Component.prototype.render === 'function') {
      monkeypatchRender(Component.prototype)
    }

    // stateless functional component
    if (!Component.prototype.render) {
      const originalStatelessComponent = Component
      if (statelessMap.has(originalStatelessComponent)) {
        Component = statelessMap.get(originalStatelessComponent)
      } else {
        Component = (...args) => {
          try {
            return originalStatelessComponent(...args)
          } catch (error) {
            logError(originalStatelessComponent, error)

            return errorPlaceholder
          }
        }
        Object.assign(Component, originalStatelessComponent)
        statelessMap.set(originalStatelessComponent, Component)
      }
    }
  }

  return originalCreateElement.call(React, Component, ...rest)
}


// allowing hot reload
const originalForceUpdate = React.Component.prototype.forceUpdate
React.Component.prototype.forceUpdate = function monkeypatchedForceUpdate() {
  monkeypatchRender(this)
  originalForceUpdate.call(this)
}
@ctrlplusb
Copy link

ctrlplusb commented Oct 6, 2016

Also worth rendering out the stack.

i.e.

console.error(errorMsg, 'Error details:', error, error.stack);

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