Why should we avoid side-effects in components constructors?

Viewed 2045

I wonder if there is a good reason not to send requests in the constructor of a component instead of in its componentDidMount hook? I've seen some interesting (but a bit incomplete) answers for: is it a good idea to fetch data within the constructor? the answer quotes an interesting bit from the documentation:

Avoid introducing any side-effects or subscriptions in the constructor. For those use cases, use componentDidMount() instead.

My question is about this point, I'm curious to understand what's the problem with side-effects like for example here sending a request in the constructor.

Maybe a quick example will help removing any ambiguity about the difference between my question and the linked one:

Sending request within the constructor

class SomeComponent extends React.Component {
    constructor(props) {
        this.request = fetch(props.someURL);
    }

    async componentDidMount() {
        const data = await this.request;
        this.setState({'data': data});
    }
}

Or sending it in componentDidMount

class SomeComponent extends React.Component {
    async componentDidMount() {
        const data = await fetch(props.someURL);;
        this.setState({'data': data});
    }
}

Or as suggested in the linked question, the definitely bad one: fetching in the constructor

class SomeComponent extends React.Component {
    constructor(props) {
        this.data = await fetch(props.someURL);
    }

    async componentDidMount() {
        this.setState({'data': this.data});
    }
}
2 Answers

Ok, there're multiple reasons:

  1. Your side effect won't always be meaningful or even runnable in server-side render, and in SSR componentDidMount hook won't be called, your teardown logic won't be run either
  2. It may trigger unintended setState which is addressed in this answer
  3. In some circumstance component will be constructed, but won't be mounted or will be mounted later, and call side effect in their constructor is meaningless

From this discussion, it seems that although you can technically trigger fetch for an API before componentDidMount, doing so isn't really useful for the majority of cases and might even be harmful:

  1. Even if in some rare case, the render takes up a huge amount of time, enough for your API to have finished before render, it still won't matter in terms of being able to actually render this newly fetched data because of the synchronous nature of javascript.
  2. It is possible for react to abort/restart a component in its render phase (ie before componentDidMount fires). This means that its possible for you to have made an extra/useless API call, only for it to be called again when react decides to restart rendering that component. This can also mess up analytics tracking, etc.
  3. It seems its gonna make it hard to migrate to newer react suspense APIs in the future (not sure about this point -- someone else might chip in in the comments)

So, the only real benefit of starting fetch in the constructor is in the rare case of the render taking a huge amount of time; time enough to lead to some actual gains in terms of preloading your data. Again, the actual state update will only be possible after this first render though.

Related