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

componentDidCatch not fired as expected #11415

Closed
kgaregin opened this issue Oct 31, 2017 · 10 comments
Closed

componentDidCatch not fired as expected #11415

kgaregin opened this issue Oct 31, 2017 · 10 comments

Comments

@kgaregin
Copy link

kgaregin commented Oct 31, 2017

hello guys! I try to build custom blog app and here is the branch with below mentioned issue.

The issue:

can't bring to live my ErrorBoundary though, doing everything like it is described in react 16 docs... Debugging shows that componentDidCatch is never called on errors (not synthetic new Error, nor real ui errors).

I use ErrorBoundary in my Navigation component

<Grid item xl={7} lg={9} md={11} sm={12} xs={12}>
    <ErrorBoundary>
        <Switch>
            <Route exact path="/" render={() => <h1>Main page under construction</h1>}/>
            <Route path="/blog/:mode?/:postID?" component={Blog}/>
        </Switch>
    </ErrorBoundary>
</Grid>

Here's ErrorBoundary component:

export class ErrorBoundary extends React.Component<{ children: JSX.Element }, IErrorBoundaryState> {
    state: IErrorBoundaryState = {hasError: false};

    componentDidCatch(error: Error, errorInfo: ErrorInfo) {
        // Display fallback UI
        this.setState({
                hasError: true,
                error: error,
                errorInfo: errorInfo
            }
        );
        // You can also log the error to an error reporting service
        console.log(error, errorInfo)
    }

    render() {
        const {children} = this.props;
        const {hasError, error, errorInfo} = this.state;
        if (hasError) {
            // You can render any custom fallback UI
            return (
                <div>
                    <h1>Something went wrong.</h1>
                    <Ghost/>
                    <hr/>
                    <h3>{error && error.toString()}</h3>
                    <br/>
                    <h3>{errorInfo && errorInfo.componentStack}</h3>
                </div>
            );
        }
        return children;
    }
}

And Blog component that is bugged natural way 😃. Here's code that cause render bug in runtime:

// const Dropzone = require('react-dropzone').default; --- proper import that works
import * as Dropzone from 'react-dropzone'; // importing this way cause render error on runtime
                                            // when Dropzone should be rendered (the case when
                                            // we go on Route with mode === CREATE or EDIT)
/*...*/
renderPostEdit = () => {
    /*...*/
    return (
        <form ref={this.saveDropZoneContainerRef} className={classes.container} noValidate autoComplete="off">
           /*...*/
            <Dropzone> //just rendering attempt enough to get exception if imported with import statement
             /*...*/
            </Dropzone>
            /*...*/
        </form>
    );
};

Also i did try:

  • with conditional throw Error inside Blog render method
  • wrap the very root application component with ErrorBoundary

and every time exatly the same effect - componentDidCatch not fired 😞

Expected behavior:

componentDidCatch fired when any of child component can't render properly due to unhandled exception, like it described in docs.

Project technology stack:

My package.json.

  • Windows, Chrome
  • React 16
  • TypeScript 2.5.3 config here and extended with this file.
  • Webpack v3.5.5 with webpack-dev-server. Here's config.
  • redux v3.7.2
  • react-redux v5.0.6
  • react-router-redux v5.0.0-alpha.8
  • material-ui v1.0.0-beta.8 as components repository
@gaearon
Copy link
Collaborator

gaearon commented Oct 31, 2017

Please create a minimal reproducing example. We get new issues every day. If we had to spend an hour trying to reproduce every issue, we wouldn't have any time left for development. :-)

Here's a good guide to doing just this:

https://stackoverflow.com/help/mcve

@gaearon
Copy link
Collaborator

gaearon commented Oct 31, 2017

(To clarify, the published demo is helpful. I didn't notice the link at the top at first. But we would still appreciate if you could extract the part that doesn't work and show it in a simpler environment, without router / libs / TypeScript / etc)

@kgaregin
Copy link
Author

Thanks for fast response! Will try to do some additional research then, gradually removing libs and ts. Maybe some of them cause problem.

@gaearon
Copy link
Collaborator

gaearon commented Oct 31, 2017

You can also try in the opposite direction, starting with a fiddle and adding two or three minimal components there.

@felansu
Copy link

felansu commented Nov 2, 2017

All states working except componentDidCatch:

import React, { Component } from 'react';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      reRendered: 'false',
      someProps: 'React Lifecycle',
      hasError: false,
      lol: { lol: `I'm working` }
    };
    console.log(`${new Date().getMilliseconds()} - * ComponentA Constructing`);
  }

  componentWillUpdate(nextProps, nextState) {
    console.log(`${new Date().getMilliseconds()} - * ComponentA Will Update`);
  }

  componentWillMount() {
    console.log(`${new Date().getMilliseconds()} - * ComponentA Will Mount`);
  }

  componentWillUnmount() {
    console.log(`${new Date().getMilliseconds()} - * ComponentA Will Umount`);
  }

  makeError() {
    this.setState({ lol: null });
  }

  render() {
    console.log(`${new Date().getMilliseconds()} - * ComponentA Rendering`);
    if (this.state.hasError) {
      return <div>Sorry</div>;
    } else {
      return (
        <div>
          <ComponentB someProps={this.state.lol.lol} />
          <button onClick={this.makeError.bind(this)}>Make error</button>
        </div>
      );
    }
  }

  componentDidMount() {
    console.log(`${new Date().getMilliseconds()} - * ComponentA Did Mount`);
    console.log('---- Changing state in component A ----');
    this.setState({ reRendered: 'true' });
  }

  componentDidCatch(error, errorInfo) {
    console.log(`${new Date().getMilliseconds()} - * ComponentA Did Catch`);
    this.setState(state => ({ ...state, hasError: true }));
  }

  componentDidUpdate() {
    console.log(`${new Date().getMilliseconds()} - * ComponentA Did Update`);
  }
}

class ComponentB extends Component {
  constructor(props) {
    super(props);

    console.log(
      `${new Date().getMilliseconds()} - > * ComponentB Constructing`
    );
  }

  componentWillReceiveProps(nextProps) {
    console.log(
      `${new Date().getMilliseconds()} - > * ComponentB Will Receive Props`
    );
  }

  componentWillUpdate(nextProps, nextState) {
    console.log(`${new Date().getMilliseconds()} - > * ComponentB Will Update`);
  }

  componentWillMount() {
    console.log(`${new Date().getMilliseconds()} - > * ComponentB Will Mount`);
  }

  componentWillUnmount() {
    console.log(`${new Date().getMilliseconds()} - > * ComponentB Will Umount`);
  }

  render() {
    console.log(`${new Date().getMilliseconds()} - > * ComponentB Rendering`);
    return (
      <b>
        {this.props.someProps} <br />
        <small>See in your console</small>
      </b>
    );
  }

  componentDidMount() {
    console.log(`${new Date().getMilliseconds()} - > * ComponentB Did Mount`);
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log(
      `${new Date().getMilliseconds()} - > * ComponentB Should Update : Returning true`
    );
    return true;
  }

  componentDidUpdate() {
    console.log(`${new Date().getMilliseconds()} - > * ComponentB Did Update`);
  }

  componentDidCatch(error, errorInfo) {
    console.log(`${new Date().getMilliseconds()} - > * ComponentB Did Catch`);
  }
}

export default App;

Package:

{
  "name": "personal",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.0.0",
    "react-dom": "^16.0.0",
    "react-scripts": "1.0.16"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

@idhard
Copy link

idhard commented Nov 2, 2017

@felansu componenetDidCatch will trigger only on lifecycle methods and render , in your example you try to throw the exception inside an event handler "makeError" , here is the documentation: https://reactjs.org/docs/error-boundaries.html#how-about-event-handlers.
I had the same issue and in order to understand the internal i opened this #11409 it may help you too.

@felansu
Copy link

felansu commented Nov 2, 2017

@idhard
Copy link

idhard commented Nov 2, 2017

what is wrong with the fiddle? , there the componenetDidCatch is triggered because the error happened in the render function not in an event handler.

@felansu
Copy link

felansu commented Nov 2, 2017

@idhard Yes, now is working.
https://github.com/felansu/personal/blob/master/src/App.js

@kgaregin
Copy link
Author

kgaregin commented Nov 3, 2017

Few months ago i decided that it would be great idea to copy react.js and react-dom.js from node modules into dist folder so they'll be near index.html without ugly paths, safe and warm. Epic plan, except they didn't update since then 🤣 God i feel so stupid. Minimal example works, going to try update on source code.

Conclusion: better not conserve libs in old-fashion manner, but pull them from node_modules like in TS guide for example.

Thanks for your time @gaearon! Will try to be more careful next time 👍
Closing issue.

@kgaregin kgaregin closed this as completed Nov 3, 2017
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

No branches or pull requests

4 participants