0

I am using a React boilerplate for a small project: https://github.com/koistya/react-static-boilerplate. This boilerplate renders most HTML server side, but I want to add a few dynamic parts to the client side.

As soon as I render the following component, it breaks on Chrome and Safari:

import React, { Component } from 'react';

class DynamicSomething extends Component {

  constructor(props, context) {
    super(props, context);
    this.state = {
      someValue: "Static"
    };
  }

  componentWillMount() {
    setTimeout(() => {this.setState({someValue: "Dynamic"});}, 1);
  }

  render() {
    return <span>{this.state.someValue}</span>;
  }
}

I get the following error, except in Firefox where it works as intended:

Uncaught TypeError: Failed to execute 'replaceChild' on 'Node': parameter 1 is not of type 'Node'.
(program):36 FIREBASE WARNING: Exception was thrown by user callback. TypeError: Failed to execute 'replaceChild' on 'Node': parameter 1 is not of type 'Node'.
    at TypeError (native)
    at Object.Danger.dangerouslyReplaceNodeWithMarkup (eval at <anonymous> (http://localhost:3000/app.js?1451913362157:2808:3), <anonymous>:140:25)
    at Object.ReactDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID (eval at <anonymous> (http://localhost:3000/app.js?1451913362157:1239:3), <anonymous>:71:27)
    at Object.wrapper [as replaceNodeWithMarkupByID] (eval at <anonymous> (http://localhost:3000/app.js?1451913362157:717:3), <anonymous>:66:21)
    at ReactCompositeComponentMixin._replaceNodeWithMarkupByID (eval at <anonymous> (http://localhost:3000/app.js?1451913362157:2862:3), <anonymous>:579:31)
    at ReactCompositeComponentMixin._updateRenderedComponent (eval at <anonymous> (http://localhost:3000/app.js?1451913362157:2862:3), <anonymous>:571:12)
    at ReactCompositeComponentMixin._performComponentUpdate (eval at <anonymous> (http://localhost:3000/app.js?1451913362157:2862:3), <anonymous>:544:10)
    at ReactCompositeComponentMixin.updateComponent (eval at <anonymous> (http://localhost:3000/app.js?1451913362157:2862:3), <anonymous>:473:12)
    at wrapper [as updateComponent] (eval at <anonymous> (http://localhost:3000/app.js?1451913362157:717:3), <anonymous>:66:21)
    at ReactCompositeComponentMixin.performUpdateIfNecessary (eval at <anonymous> (http://localhost:3000/app.js?1451913362157:2862:3), <anonymous>:421:12) 

It does work when I call setState directly without the setTimeout or in my actual case a callback function from a Firebase call.

It always works in Firefox.

I'm using "react": "^0.14.0", and "babel": "^5.8.29",.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
Kamiel Wanrooij
  • 12,164
  • 6
  • 37
  • 43

1 Answers1

2

You shouldn't asynchronously set state in componentWillMount, use componentDidMount for that instead. Actually you're not supposed to call setState at all before the component is mounted (so therefore doing it in componentWillMount is risky even in an async callback).

https://facebook.github.io/react/docs/component-specs.html#mounting-componentdidmount

If you want to integrate with other JavaScript frameworks, set timers using setTimeout or setInterval, or send AJAX requests, perform those operations in this method.

You can read a more in depth reason here: https://stackoverflow.com/a/29995104/414062

Community
  • 1
  • 1
Dominic
  • 62,658
  • 20
  • 139
  • 163
  • 1
    Awesome, that did the trick! Also explains why my first callback usually worked (setting up the websocket connection), but the second callback didn't (websocket already set up, so the reply was much faster). – Kamiel Wanrooij Jan 04 '16 at 15:58
  • 1
    I was caught off guard because of the comments in here: https://facebook.github.io/react/docs/component-specs.html#mounting-componentwillmount mentioning calling `setState` in `componentWillMount` and that `render` will pick up the new state. Ofcourse this does not apply to asynchronous callbacks. – Kamiel Wanrooij Jan 04 '16 at 16:00
  • 1
    Yeah it threw me too at first! – Dominic Jan 04 '16 at 21:48