gatsbyjs / gatsby

The best React-based framework with performance, scalability and security built in.
https://www.gatsbyjs.com
MIT License
55.28k stars 10.31k forks source link

[Production Bug] uncaught domException: Failed to execute 'insertBefore' on 'Node' #11662

Closed CharlyMartin closed 5 years ago

CharlyMartin commented 5 years ago

Description

I built a custom image slider (Carousel) component using gatsby-image. Sometimes, when clicking to access the next/previous image, the website crashes and goes blank, with an error in console.

I can't seem to pinpoint the triggering event, it happens randomly. That seems to happen when the website is inactive for some time, like 60 secs or so, or when it just loaded. It happened with both Chrome and Firefox, in their latest stable versions.

It also happened in a dev environment but no there were no errors in the terminal, only in the browser's console.

Full error message in Chrome's console:

react-dom.production.min.js:179 DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
    at Ti (https://www.hubsy.fr/app-694f816022357852d6d4.js:1:127811)
    at Ua (https://www.hubsy.fr/app-694f816022357852d6d4.js:1:146795)
    at Ma (https://www.hubsy.fr/app-694f816022357852d6d4.js:1:144255)
    at Na (https://www.hubsy.fr/app-694f816022357852d6d4.js:1:143600)
    at Da (https://www.hubsy.fr/app-694f816022357852d6d4.js:1:149931)
    at Pn (https://www.hubsy.fr/app-694f816022357852d6d4.js:1:88881)
wi @ react-dom.production.min.js:179

react-dom.production.min.js:219 Uncaught DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
    at Ti (https://www.hubsy.fr/app-694f816022357852d6d4.js:1:127811)
    at Ua (https://www.hubsy.fr/app-694f816022357852d6d4.js:1:146795)
    at Ma (https://www.hubsy.fr/app-694f816022357852d6d4.js:1:144255)
    at Na (https://www.hubsy.fr/app-694f816022357852d6d4.js:1:143600)
    at Da (https://www.hubsy.fr/app-694f816022357852d6d4.js:1:149931)
    at Pn (https://www.hubsy.fr/app-694f816022357852d6d4.js:1:88881)

Steps to reproduce

When clicking on one of the arrows, it should display the next/previous image in the slider. Here's the live website: https://www.hubsy.fr/. The image slider components can be found at:

Here's the Github repo: https://github.com/CharlyMartin/hubsy-v2.3, the image slider component can be found at: https://github.com/CharlyMartin/hubsy-v2.3/blob/master/src/components/image_slider.jsx

Expected result

It should display the next/previous image, and it does most of the time.

Actual result

When it fails, the page goes blank and I have to refresh it to get the website back.

screenshot 2019-02-09 at 18 15 19

Environment

  System:
    OS: macOS 10.14.2
    CPU: (4) x64 Intel(R) Core(TM) i5-6287U CPU @ 3.10GHz
    Shell: 5.3 - /bin/zsh
  Binaries:
    Node: 11.9.0 - /usr/local/bin/node
    Yarn: 1.13.0 - /usr/local/bin/yarn
    npm: 6.5.0 - /usr/local/bin/npm
  Languages:
    Python: 2.7.10 - /usr/bin/python
  Browsers:
    Chrome: 71.0.3578.98
    Firefox: 65.0
    Safari: 12.0.2
  npmPackages:
    gatsby: ^2.0.19 => 2.0.106 
    gatsby-cli: ^2.4.6 => 2.4.8 
    gatsby-image: ^2.0.15 => 2.0.29 
    gatsby-plugin-google-tagmanager: ^2.0.9 => 2.0.9 
    gatsby-plugin-i18n: ^0.4.2 => 0.4.2 
    gatsby-plugin-manifest: ^2.0.5 => 2.0.15 
    gatsby-plugin-offline: ^2.0.5 => 2.0.22 
    gatsby-plugin-react-helmet: ^3.0.0 => 3.0.5 
    gatsby-plugin-sharp: ^2.0.7 => 2.0.19 
    gatsby-plugin-sitemap: ^2.0.3 => 2.0.4 
    gatsby-source-airtable: ^2.0.2 => 2.0.3 
    gatsby-source-filesystem: ^2.0.4 => 2.0.18 
    gatsby-transformer-remark: ^2.1.17 => 2.2.2 
    gatsby-transformer-sharp: ^2.1.4 => 2.1.13 
pieh commented 5 years ago

Hey @CharlyMartin! Did you, by any chance, fixed this issue since opening this bug report? I tried to reproduce, but wasn't really able to.

In any case, I tried to follow stack trace you provided and those errors seem to come from react package, so it doesn't really provide much insight what component is actually causing this. My best guess would be that we might have some unhandled scenarios in gatsby-image that are pretty difficult to trigger with mounting/unmounting and setting state? Will continue to test, but please let me know as well if you are still able to reproduce

CharlyMartin commented 5 years ago

Hi @pieh, thanks for your answer!

I did have the same issue a few times since lodging this issue, it seems to be the first times one of the arrows is clicked. It's happening even more on mobile for some reasons.

I'm using setState a few times in this component to render the proper image. Do you think it might be the cause of the problem?

I've read that setState functions are asynchronous (like 99% of Javascript 🙂) so relying on the same state several times in the same function can be dangerous. Do you think that could be the problem?

Here's the code:

// Libraries
import React from 'react';
import Img from 'gatsby-image';

// Components
import Image from './image';

// CSS
import '../css/components/image_slider.css';

// Images
import caret from '../images/icons/caret.png'

class ImageSlider extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      selectedImg: 0,
    }
  }

  getLastIndex() {
    const result = this.props.images.length - 1;
    // console.log(result);
    return result;
  }

  isLast() {
    const result = (this.getLastIndex() === this.state.selectedImg);
    // console.log(result);
    return result;
  }

  isFirst() {
    const result = this.state.selectedImg === 0
    // console.log(result);
    return result;
  }

  getNextSlide() {
    if (this.isLast()) {
      this.setState({selectedImg: 0});
      return;
    }

    this.setState({selectedImg: this.state.selectedImg += 1});
  }

  getPreviousSlide() {
    if (this.isFirst()) {
      this.setState({selectedImg: this.getLastIndex()});
      return;
    }

    this.setState({selectedImg: this.state.selectedImg -= 1})
  }

  renderImage() {
    const images = this.props.images;
    const index = this.state.selectedImg;
    const currentImg = images[index].node.childImageSharp.fluid;
    const style = {
      position: "absolute",
      left: 0,
      top: 0,
      width: "100%",
      height: "100%"
    }

    return (
      <Img
        fluid={currentImg}
        sizes={currentImg}
        title="Hubsy Café"
        alt="Hubsy Café"
        className="gatsby-image-element"
        style={style}
        fadeIn={true}
      />
    )
  }

  render() {
    return (
      <div className={`hero-image ${this.props.class}`}>
        {this.renderImage()}

        {this.props.children}

        <div className="arrows">
          <div className="arrow-container" id="left" onClick={this.getPreviousSlide.bind(this)}>
            <div className="arrow">
              <Image src={caret} />
            </div>
          </div>

          <div className="arrow-container" id="right" onClick={this.getNextSlide.bind(this)}>
            <div className="arrow">
              <Image src={caret} />
            </div>
          </div>
        </div>
      </div>
    );
  }

};

export default ImageSlider;
wardpeet commented 5 years ago

Thanks so much for opening this issue! As stated, this is slightly tangential to Gatsby.

The issue is inside your Alert.jsx component. You're removing an alert but react does not know that because you're using document.addeventListener

import React from 'react';
import cross_white from '../images/icons/cross_white.png';
import Image from './image';

import { closeAlert } from '../utilities/close_alert';

import '../css/components/alert.css'

class Alert extends React.Component {
  constructor(props) {
    super(props)

    this.state ={visible:true}
  }

  close() {
     this.setState({ visible: false });
  }

  render() {
    if (!this.state.visible) { return null; }
    return (
      <div className="alert">
        <div className="container">
          <p>{this.props.content}</p>
          <div className="cross-container">
            <Image src={cross_white} onClick={this.close.bind(this)} />
          </div>
        </div>
      </div>
    );
  }
};

export default Alert;

Thanks for using Gatsby :muscle:

CharlyMartin commented 5 years ago

Thanks a lot @wardpeet for this, I will try it out but it looks good 🙏