question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Errors in render() persist even after removing component

See original GitHub issue

I reproduced this with a simple example (source code below) <App> is the parent component, <Profile> is the child

  1. Clicking the button in Profile sets state.error to true which successfully renders the error page.
  2. If instead I cause a TypeError in Profile (a plausible runtime occurrence if an object property is null), the error page does not show, even though state.error is still set to true. e.g. change to <div class={style.profile.x.y}> in Profile
  3. If I repeat 2) but set default state.error to true (so that Profile is never rendered) the error page successfully renders.

The difference between 2) and 3) suggests there might be an issue replacing a component that rendered with an error.

app.js

import { h, Component } from 'preact';
import Profile from './profile';
export default class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      error: true
    };

    window.onerror = () => {
      this.setState({
        error: false
      });
    };
  }

  throwError = e => {
    throw new Error('error generated');
  };

  render() {
    console.log('this.state.error', this.state.error);
    return (
      <div id="app">
        {this.state.error ?
          <div>{'error, please refresh'}</div> :
          <Profile throwError={this.throwError}/>
        }
      </div>
    );
  }
}

profile.js

import { h, Component } from 'preact';
import style from './style';

export default class Profile extends Component {
  render({throwError}) {
    return (
      <div class={style.profile}>
        <h1>Profile.</h1>
        <p>This is the profile page</p>
        <button onclick={throwError}>{'Throw Error'}</button>
      </div>
    );
  }
}

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:2
  • Comments:10 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
angus-ccommented, Feb 14, 2017

Wrote a wrapper based on yours (and then conditionally don’t render child components when in error state). It seems to be working well but feedback welcome.

Thanks again for your help!


import {options} from 'preact';
import window from 'global/window';

export default function handleRenderErrors() {

  const originalVnode = options.vnode;
  options.vnode = vnode => {
    if (typeof vnode.nodeName === 'function') {
      wrapComponent(vnode.nodeName);
    }
    if (originalVnode) {
      originalVnode(vnode);
    }
  };

  function wrapComponent(Component) {
    if (Component.prototype && Component.prototype.render && !Component.__safe) {
      Component.__safe = true;   // only wrap components once
      const originalRender = Component.prototype.render;
      Component.prototype.render = function render(...args) {
        try {
          return originalRender.apply(this, args);
        } catch (e) {
          window.onerror(`Error rendering component ${this.constructor.name}`, null, null, null, e, true);
          return null;
        }
      };
    }
  }
}
1reaction
developitcommented, Feb 6, 2017

An exception encountered during rendering (essentially, within render() or a PFC) halts rendering but leaves pending renders queued. The next setState() call will attempt to re-render the broken components.

Philosophically, what could Preact do here? The child component cannot be rendered, since its render throws and thus has no return value. However, skipping its render would be a silent error.

FWIW I’ve actually used this behavior to implement error boundaries. Basically, you install a VNode hook that automatically wraps all component prototype methods in error softening via console.error(). A naive version looks something like this:

import { options } from 'preact';

let old = options.vnode;
options.vnode = vnode => {
  if (typeof vnode.nodeName==='function') {
    wrapComponent(vnode.nodeName);
  }
  if (old) old(vnode);
};

function wrapComponent(Component) {
  let proto = Component.prototype;
  if (!proto || !proto.render || Component.__safe) return;
  Component.__safe = true;   // only wrap components once
  for (let i in proto) if (i!=='constructor' && typeof proto[i]==='function') {
    proto[i] = wrapMethod(proto[i]);
  }
}

const wrapMethod = fn => function() {
  try {
    return old.apply(this, arguments);
  } catch(e) { console.error(e); }
};

Interestingly, because the behavior is entirely customizable via that hook, you could install a development error handler that intentionally blows up or console.error()'s, but then switch to a production error handler that sequesters logging and attempts to continue rendering if possible to avoid white-screening.

That brings me to the crux of the issue though: if a component errors, I’m not sure that there is a great way to recover or roll back. Any way you go about it, an error is going to produce incorrect UI state that was not expected.

Just my thoughts, I had been working on something like this over the past little while so it’s fresh in my brain. Happy you raised the issue here, maybe we can come up with a great open-source solution (either within preact or as a library).

Read more comments on GitHub >

github_iconTop Results From Across the Web

Errors in render() persist even after removing component #531
An exception encountered during rendering (essentially, within render() or a PFC) halts rendering but leaves pending renders queued. The next ...
Read more >
A React component suspended while rendering, but no ...
It was using <Suspense /> element in the component but I removed it. Then application is crashed with above error.
Read more >
5 Ways to Avoid React Component Re-Renderings
1. Memoization using useMemo() and UseCallback() Hooks. Memoization enables your code to re-render components only if there's a change in the props. With...
Read more >
React Components rendered twice — any way to fix this?
Let's find out if there is a way to avoid this problem by trying different implementations. A) Functional Component with useState. function App()...
Read more >
How to solve too many re-renders error in ReactJS?
“Too many re-renderers” is a React error that happens after you have reached an infinite render loop, typically caused by code that in...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found