0

I'm exploring React 16. One of the new features of this version is Async Rendering (aka Fiber). It is said that componentWillMount is unsafe because it can be called multiple times and sometimes it will cause unwanted side effects. I'd read this document https://github.com/acdlite/react-fiber-architecture and watched this video https://www.youtube.com/watch?v=aV1271hd9ew&feature=youtu.be but I can't find working example of such behaviour of componentWillMount.

In this questions:

it is said that componentWillMount may be called several times when high priority event occurs during rendering process.

So I tried to make it myself using create-react-app and React.js 16.11.0. My idea is to build big react tree and add css animation on root component.

JSX:

class RecursiveComponent extends React.Component {
  componentWillMountCalledTimes = 0;

  componentWillMount() {
    this.componentWillMountCalledTimes++;
    if (this.componentWillMountCalledTimes > 1) {
      console.log(`Mounting ${this.props.depth} call ${this.componentWillMountCalledTimes}`);
    }

  }

  render() {
    if (this.props.depth > 0) {
      if (this.props.depth % 2) {
        return (
          <div>
            <RecursiveComponent depth={this.props.depth - 1} />
          </div>
        );
      } else {
        return (
          <span>
            <RecursiveComponent depth={this.props.depth - 1} />
          </span>
        );
      }
    } else {
      return <div>Hello world</div>;
    }
  }
}

class App extends React.Component {
  state = {
    depth: 1000,
  }

  componentDidMount() {
    setInterval(() => {
      this.setState({ depth: 1001 + this.state.depth % 10 });
    }, 1000);
  }

  render() {
    return (
      <div className={'App'}>
        <RecursiveComponent depth={this.state.depth} />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));

styles:

.App {
  width: 400px;
  height: 400px;
  background-color: red;

  animation-duration: 3s;
  animation-name: slidein;
  animation-iteration-count: infinite;
}

@keyframes slidein {
  from {
    margin-left: 300px;
  }

  to {
    margin-left: 0;
  }
}

I expect smooth animation and rare console output but there is no messages in console. What is my mistake? How can I demonstrate multiple calls of componentWillMount function?

Upd:

As @DoXicK mentioned Async Rendering is disabled by default in current version of React.JS. I'd followed by this guide https://reactjs.org/docs/concurrent-mode-adoption.html and write such example

import React from 'react';
import './App.css';

class Caption extends React.Component {
  componentWillMountCalledTimes = 0;

  componentWillMount() {
    wait(100);
    this.componentWillMountCalledTimes++;
    if (this.componentWillMountCalledTimes > 1)
      console.log(`Mounting ${this.props.depth} call ${this.componentWillMountCalledTimes}`);
  }

  render() {
    return <div>{this.props.children}</div>;
  }
}

class App extends React.Component {
  state = { counter: 0 }

  componentDidMount() {
    setInterval(() => {
      this.setState({ counter: this.state.counter + 1 });
    }, 100);
  }

  render() {
    if (this.state.counter % 10 === 0) {
      return <div>Empty</div>;
    } else {
      return (
        <div className={'App'}>
          <Caption>{'Hello 1'}</Caption>
          <Caption>{'Hello 2'}</Caption>
          <Caption>{'Hello 3'}</Caption>
        </div>
      );
    }
  }
}

function wait(time) {
  const start = Date.now();

  while (Date.now() - start < time) {
  }
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);

I'd used CSS from the example above. I'd expected to get at least one message about multiple call of componentWillMount on one element (because each element renders more than , but I had no luck.

I think I'm missing something but I don't understand what.

Egor Baranov
  • 11
  • 1
  • 5
  • you need to add constructor – elad BA Nov 14 '19 at 09:47
  • and BTW you don't have any reason to try and learn something that is deprecated what would it give you? you might as well go and learn another dead coding language that will give you nothing. – elad BA Nov 14 '19 at 09:50
  • Does this answer your question? [Why might componentWillMount be called multiple times with React Fibre?](https://stackoverflow.com/questions/53107319/why-might-componentwillmount-be-called-multiple-times-with-react-fibre) – DoXicK Nov 14 '19 at 10:30
  • @eladBA you don't need a constructor. And componentWillMount won't be deprecated. Learning from a deprecated syntax, if that were the case, would still give valuable insights in how the framework evolved and why changes have been introduced. – DoXicK Nov 14 '19 at 10:33
  • @DoXicK thanks for your answer, but unfortunately this link does not answer my question. As I've already mentioned in the question I saw your link and it only gives some theoretical assumption and I want a practical proof of it. I've tried to make one by myself but it does not work/ – Egor Baranov Nov 14 '19 at 12:08
  • @EgorBaranov i think these features are still hidden behind experimental flags and will only come available once concurrent-mode is finalized. For more info check the scheduler and its usage: https://github.com/facebook/react/blob/df8db4e005573d3cd793f9a6406e97e6014d5545/packages/scheduler/src/Scheduler.js#L217 – DoXicK Nov 14 '19 at 14:37

1 Answers1

0

I've found example in this article: https://0e39bf7b.github.io/posts/react-journey-componentwillmount-in-concurrent-mode/

let lastCounter = 0; // Global variable for multiple mounts detection

class Counter extends React.Component {
  componentWillMount() {
    if (lastCounter === this.props.value) {
      console.log(`mount counter with value = ${this.props.value} multiple times`);
    }
    lastCounter = this.props.value;

    // Syncronously wait for 100ms to emulate long work
    const start = Date.now();
    while (Date.now() - start < 100);
  }

  render() {
    return <div>{this.props.value}</div>;
  }
}

class App extends React.Component {
  state = { counter: 0, showGreetings: true };

  componentDidMount() {
    this.interval = setInterval(() => {
      this.setState({ counter: this.state.counter + 1 });
    }, 500);
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  toggleGreetings = () => {
    this.setState({ showGreetings: !this.state.showGreetings });
  };

  render() {
    // Use key attribute to force React.JS to remount Counter component
    return (
      <>
        <button onClick={this.toggleGreetings}>Toggle greetings</button>
        {this.state.showGreetings && <h1>Hello world!</h1>}
        <Counter value={this.state.counter} key={`counter-${this.state.counter}`} />
        <div>{this.state.counter2}</div>
      </>
    );

  }
}

// Instead of regular initialization
// ReactDOM.render(<App />, document.getElementById('root'));
// use Concurrent Rendering for this component
ReactDOM.createRoot(document.getElementById('root')).render(<App />);

My main misunderstanding was the idea that componentWillMount will be called multiple times for the same instance and as it's described in the article new instance of component is created and componentWillMount is called for it. Now the idea is clear.

Egor Baranov
  • 11
  • 1
  • 5