useState with `new` keyword as parameter

2.1k views Asked by At

I'm was building a separated service that deals with a complex things, the service is a class that, just for testing proposes, I mock inside a useState.

The point is that I forgot a console.log inside the constructor and realize that the class constructor is called so many times as the component is re-rendered. That behavior don't lead to unexpected behavior or something like that, but I'm asking myself WHY this is happening, as I know things declared inside a useState on it's call don't repeat itself, but apparently I'm wrong what leads to the questions below.

  1. Why this happens? (I don't find any docs about this specific case)
  2. Does this affect memory or processing? (Since the class is re-instantiated many times)
  3. The garbage collector collect that?

I create a little sandbox to example what I'm saying, you can see that the "Called" word is displayed many times on console, and keeps displaying clicking on the button. https://codesandbox.io/s/new-class-inside-usestate-w9et3?file=/src/App.js

3

There are 3 answers

0
Dennis Vash On BEST ANSWER

It is a common mistake and somewhat not explicitly mentioned in React docs.

On each render the body of function component executed. Logging from the constructor is the expected behaviour, because writing such code:

const [example] = useState(new Foo());

Will results calling new Foo() on every render, yes, although it's result not considered by useState hook.


Therefore you would like to make lazy initial as you want it to be called once:

const [example] = useState(() => new Foo());

The initialState argument is the state used during the initial render. In subsequent renders, it is disregarded. If the initial state is the result of an expensive computation, you may provide a function instead, which will be executed only on the initial render.

Edit Q-64131447-LazyInitial

0
hackape On

Cus useState(/* whatever expression here */) is still and JS expression. Nothing to do with React, it's just how JS work, the thing inside parens () will always evaluate.

Let's leave useState aside, think about some randomFunction(), if you do:

setInterval(() => {
  randomFunction(new RandomClass());
}, 1000)

Will RandomClass be instantiated every 1 sec? Of course it will.

Same thing happens in React.

function MyApp() {
  const [myClass, setMyClass] = useState(new RandomClass())
  // ...
}

Everytime <MyApp /> got re-rendered, the function must re-run, so must RandomClass() be re-newed. What mislead you is the effect of useState(). It takes the expression passed inside parens as it's initial value at first render, and will discard whatever got passed in in following re-render. But that expression still evalutate.

0
95faf8e76605e973 On

It is simply an expression and it evaluates to an instance of that class. It just so happens that the constructor also logs some data. The same behaviour can be replicated by using an iife i.e., useState((function(){console.log("Called")})());. That does not necessarily mean it will set a new instance of that class for your state because it has already been set the 1st time your component rendered.

This theory can be tested in a useEffect

React.useEffect(()=>{
  console.log(exemple === exemple)
})

you will see that it returns true because it is a reference to the same class and no state change has occured. The impact on your app in this setting is the time complexity increases because it does instantiate a new class every render occurs.