Why should we avoid side-effects in components constructors?
Asked Answered
P

2

6

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});
    }
}
Perineuritis answered 3/4, 2020 at 21:12 Comment(8)
Does this answer your question? Fetching data in constructorPigg
No, it doesn't completely (the question is different and therefore answers are not accurate for mine). I though this was clear in my question: I linked this question in my own and explained why the two questions differ with my first code example. I'll add a third piece of code to insist again on this.Perineuritis
I am aware that you linked it, however to me this question is not sufficiently different than the other you linked, so I have voted to close it as a duplicate of that one (the wording is stack overflow's automatic comment generated by doing so). You may wish to edit your question to highlight the way in which it is different, and why the answers on the other question are insufficient.Pigg
Sorry I don't understand what you don't understand.Perineuritis
From the answer on that question with 12 upvotes: "Constructor is called before the component is mounted...your component will sometimes not re-render if you call setState in the constructor." What specific question do you have about it that isn't covered by that? You seem to have this mental model that synchronously maps your React component to the DOM but that isn't how React works: it batches DOM access as a performance optimization, and it doesn't let you call setState on an unmounted component to among other things help with debugging. All of this is covered in the docs.Pigg
This part of the answer has nothing to do with my question, I don't call setstate in the constructor ... The part that isnt answered is the one I asked about "what's the problem with side effects, like for example in this case emiting an http request".Perineuritis
What if React makes multiple copies of your component but only renders one of them? Do you want to fire that off multiple times? What about the speculative execution inherent in the new Suspense API? Your code in typescript wouldn't even compile because you're changing the type signature of an overridden method. In short, when the docs of a framework/library say don't do this thing, it's a good idea not to do it. And it doesn't matter why: if they tell you don't do it that means it's part of the API contract. There may not even be a reason but they want the flexibility of not having to ...Pigg
support a specific edge case or avoid problems created by performance optimizations. I am however retracting my close vote because it's not a duplicate of the other.Pigg
S
2

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
Smarmy answered 5/8, 2020 at 7:12 Comment(0)
R
0

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.

Ruth answered 20/9, 2022 at 5:41 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.