useState with `new` keyword as parameter - reactjs

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.
Why this happens? (I don't find any docs about this specific case)
Does this affect memory or processing? (Since the class is re-instantiated many times)
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

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.

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.

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.

Related

React state change is one step behind

A common problem in React, especially among beginners (like me). This is due to the fact that setState is an asynchronous function and React does not change values one by one, but accumulates some pool if it is not otherwise instructed. (please correct me if the wording is wrong)
So here's the question. One simple solution is to put the desired state into the useEffect dependency array.
For example, I have such a component:
const [first, setFirst] = useState()
const [second, setSecond] = useState()
useEffect(() => {
Something logic by first and second...
}, [first])
Here, the useEffect is executed every time the first state changes. At the same time, at the moment of its operation, the second state is not relevant, next time it will become as it should be now, and so on. Always one step behind.
But if I add a second state to the useEffect dependency array, then everything works as it should.
useEffect(() => {
Something logic by first and second...
}, [first, second])
At the same time, it is not necessary that the useEffect work on changing the second state, I added it only in order to have an up-to-date version inside the useEffect. Can I use the useEffect dependency array this way?
if you use useEffect with an empty array it will act as componentDidMount and componentWillUnmount so it will only invoke itself on first component creation and when it is about to unmount.
https://reactjs.org/docs/react-component.html#componentdidmount
Here I got the problem You are facing, I am not fully sure, but In my case I was console.log(result) withing the fucntion that was changing state, but it was always one step behind. why was that? because in React it is considered as side effect. So If you console.log(result) in useEffect passing the value in dependency array then, it will console log the same value that instantly changed.
In backgorund the state is updating exactly the same time but useEffect detects it as exactly as it is changed.
You can write any logic in useEffect as well or in the function which you are updating.
So there should not be any problem writing logic in the function which you are callilng on click.

useEffect called when a variable not in the dependancy list is updated (causing an infinite loop)

I'm trying to do an ajax call when a state changes, and then set another state to the result of that ajax call.
const [plc, setPlc] = useState(null);
const [tags, setTags] = useState([]);
...
useEffect(()=>{
if(plc != null) {
myAjaxPromiseFunction(plc).catch((err)=>{
console.log(err);
}).then((tags)=>{
setTags(tags);
});
}
}, [plc]);
For some reason, this results in an infinite loop. However, when I remove the setTags(tags); statement it works as expected. It appears that the setTags function is causing the effect hook to update, but tags is not a part of the dependencies of this effect hook.
FYI, the tags variable is supposed to be a list of objects.
EDIT:
I have one other useEffect in my code.
useEffect(() => { // Update which tags are logged
if(plc != null) {
anotherAjaxFunction(plc, tags).catch(err => {
toaster.show({
message: err,
intent: "danger"
});
}).then(tags => {
setTags(tags);
});
}
}, [plc, updateLoggedFlag]);
However, this useEffect is not dependant on tags, and the issue still occurs if I remove this block of code.
Side note: The updateLoggedFlag is a variable I was planning to use to force the effect to update as needed, but I haven't used it anywhere yet.
Another EDIT: Link to reproduction using code sandbox: https://codesandbox.io/s/cranky-ives-3xyrq?file=/src/App.js
Any ideas? Thanks for the help.
In short: move TagColumn outside of App, as it is here.
The problem is not connected to the effects declaration, it's due to the component declaration.
In your sandbox, the TagColumn was defined inside of App. It means that every time App was rendered (in particular, every time its state is changed), the const TagColumn was assigned a different value. React can't see that it is the same component every time - after all, it's not really the same, since the captured scope is changing.
So, App renders a new TagColumn. React sees that component is created from scratch, renders it, and, in particular, invokes its hooks, including every useEffect. Inside useEffect, you use callback to change the state of App. App rerenders, creates yet another component TagColumn, which is again rendered for the first time in its life, calling every useEffect... and the cycle continues.
In general, you shouldn't capture anything non-constant in the functional component scope. Instead:
if the value is generated by external context (App, in this case) - pass it as props;
if the value is generated by the component itself - use useState, useRef or other hooks, as necessary.
The problem I think with your code is that it does not include tags in the dependency array. Conceptually, this should work fine, as mentioned before by other developers, but React documentation states that, all the state variables and props used inside the callback, should be present inside the dependency array, otherwise it can cause bugs. Have a look at it here. Hooks FAQ

question on code snippet useEffect with fetching state

I am reading this code snipper https://reactjs.org/docs/hooks-faq.html#how-can-i-do-data-fetching-with-hooks linked from react documentation page https://reactjs.org/docs/hooks-faq.html#how-can-i-do-data-fetching-with-hooks
I don't understand this piece of code with the returned function that modifies the ignore variable
useEffect(() => {
let ignore = false;
async function fetchData() {
const result = await axios('https://hn.algolia.com/api/v1/search?query=' + query);
if (!ignore) setData(result.data);
}
fetchData();
return () => { ignore = true; }
}, [query]);
once the function that is passed to useEffect is called a second time, doesn't ignore reset back to false leaving the ignore = true completely useless. The code above does not compute for me. Thanks for explaining.
The ignored variable is in function scope, current effect's ignored will be a different instance with the one in the next effect.
If an effect returns a cleanup function, React invokes it when the component unmounts or before the effect gets re-run due to a dependency change (if query were to change in this case).
The axios fetch call is asynchronous, so it's possible that the component will unmount or a dependency (e.g. query) will have changed before the data request completes.
If that happens, the returned function will have set ignore to true and the fetchData function won't try to call setData on the unmounted component. (Attempting to update state on an unmounted component causes an error.)
From the docs:
When exactly does React clean up an effect? React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time.
Due to the way javascript closures work, the cleanup function can access the ignore variable in the enclosing scope.
When the effect runs again, a new ignore variable gets declared with a new cleanup function that references the new variable.
Your understanding of the functional call and variable assignment is correct, but you forgot the power of the async call :)
useEffect is called whenever it is needed (e.g. componentDidMount, componentDidUpdate by query keyword), so we roughly have
ignore = false
function fetchData is now defined
function fetchData is called
keep the reset of useEffect in your mind for a sec.
let's say something happens which causes a re-render e.g. prop-change (but other than query) or even call that setData with newly fetched data.
your useEffect will affect nothing, cause query shallow-comparison can not find a change with the previous one. So the purpose of the ignore is not involved in this case, cause it is not going to change at all.
Also if the query change in any way the function execution would roughly (ignoring event loop, macro task everything for the moment) be something like:
// ignore is false and for some reason query changes...
(() => { ignore = true })() // ignore = true
// continue running useEffect again.
// lets think nothing else will happend in gap
ignore = false // ignore = false
The only possible scenario I can think about is when your component is going to be unmounted when your useEffect fallback is being called. So here is when that async keyword wants its dignity back 🙌️.
Remember: The async function is not return anything by your command.
Imagine the data arrive when your component is fully removed from the DOM, here is the role which that ignore=true plays in this useEffect.
Preventing setData on unmounted component

What's the idea behind useState functionality in ReactJS?

I am learning ReactJS and I understand the benefits of functional programming. However, I am a little concerned about the actual useState approach. If we assume a variable to be changed, then why declaring it const in the first place?
I see that I can simply use let number = 4; and then render it like this <p>{number}</p>. What I cannot do however is to re-render it automatically just by changing it, for example using onClick event like this <p onClick={() => ++number }></p>. Why is it that so? Is there a specific reason I am missing why it was implemented the way it is? I mean why the developers have decided that if the value needs to be re-rendered upon change, then it must be a const value declared with the help of useState functionality?
I am asking this because I am suspecting I am missing some good points behind this and I would like to understand them.
The variable is declared as a const because you are not allowed to set it directly. In React the state itself is immutable. You are just allowed to change it over setState (or setNumber in your case) or with actions if you use redux.
But why is that? It may seem unnecessary cumbersome in the beginning
First of all, if your variable number changes, react has to trigger a rerender.
If the state is mutable, it requires data-binding because if the number is set, it has to update the view.
In javascript, data-binding works for simple objects, but not well for arrays. Vue.js for example, as an alternative that uses two-way data binding, had a lot of trouble in its early versions when dealing with arrays. That's why there are now only seven predefined methods to interact with arrays (which they added later to solve that problem). Vue Js Array Methods
So a simple reason to declare the state as const is that it works better with arrays. And if you watch the example you gave, setNumber(number + 1) is not that much more to write than number++. But setState(newArray) works, and newArray[i] = newElement would not work, because due to javascript limitations this cannot trigger a rerender.
Secondly, it is a nicer design concept. Think of your component as a function, that returns a view to a state. And if the state changes, you get a new view. It simplifies relationships between properties in your component. If you were allowed to change your state while rendering your component, it would create a mess.
The problem is that you're thinking of a functional component as if it was stateful. But it isn't. It's a function and once you run it, that's it.
Take this example:
function useState() {
let value = 1
function setValue(v) {
value = v
}
return [value, setValue]
}
function myFunction () {
const [value, setValue] = useState(); // <----- we use const here
return null
}
Even though we're using const, the value variable only exists within the function, once the function returns that's it. It's the same for components.
The actual value of value is stored in a whole different scope, where useEffect has access to.
Here's a deep dive on how react works internally if you're interested
React works in render cycles, i.e. some state is declared, the DOM (UI) is computed during the "render phase", and then flushed to the actual DOM during the "commit phase".
Within each cycle state is considered constant (const in JS simply means a variable can't be assigned a new value, you could just as easily declare it with let or var instead and react would work the same) but for react's purpose, state is constant during a render cycle. When it is updated via one of the state update methods, react then kicks off another render cycle (update state, compute diff, commit to DOM) and re-renders when necessary.
This process is important and the reason why external state mutations are considered anti-pattern, it goes against the react workflow and leads to buggy code or worse, UI that doesn't update as expected.
React component lifecycle
I cannot do however is to re-render it automatically just by changing
it, for example using onClick event like this <p onClick={() => ++number }></p>. Why is it that so?
React state updates use a process called reconciliation to figure out what changed and what to re-render. In really simplistic terms, when react state is updated it is updated with a new object reference so a shallow object comparison can more quickly detect that the component state updated.
Declaring state and doing ++number simply changes the value but not the reference, and would be considered a state mutation, an anti-pattern in react.

useMemo vs useState for React hooks constants

Defining a calculated (initialized) constant using React hooks can be performed in two ways that seem functionally equivalent. I don't want to get into the use cases for this, but suffice to say that there are cases where a constant value can be derived from initial props or state that isn't expected to change (think route data, bound dispatch, etc).
First, useState
const [calculatedConstant] = useState(calculateConstantFactory);
Second, useMemo
const calculatedConstant = useMemo(calculateConstantFactory, []);
Both of these seem functionally equivalent, but without reading the source code, I'm not sure which is better in terms of performance or other considerations.
Has anyone done the leg work on this? Which would you use and why?
Also, I know some people will recoil at the assumption that state can be "considered constant". I'm not sure what to tell you there. But even without state, I may want to define a constant within a component that has no state at all, for example, creating a block of JSX that doesn't change.
I could define this outside of the component, but then it's consuming memory, even when the component in question is not instantiated anywhere in the app. To fix this, I would have to create a memoization function and then manually release the internal memoized state. That's an awful lot of hassle for something hooks give us for free.
Edit: Added examples of the approaches talked about in this discussion.
https://codesandbox.io/s/cranky-platform-2b15l
You may rely on useMemo as a performance optimization, not as a semantic guarantee
Meaning, semantically useMemo is not the correct approach; you are using it for the wrong reason. So even though it works as intended now, you are using it incorrectly and it could lead to unpredictable behavior in the future.
useState is only the correct choice if you don't want your rendering to be blocked while the value is being computed.
If the value isn't needed in the first render of the component, you could use useRef and useEffect together:
const calculatedConstant = useRef(null);
useEffect(() => {
calculatedConstant.current = calculateConstantFactory()
}, [])
// use the value in calcaulatedConstant.current
This is the same as initializing an instance field in componentDidMount. And it doesn't block your layout / paint while the factory function is being run. Performance-wise, I doubt any benchmark would show a significant difference.
The problem is that after initializing the ref, the component won't update to reflect this value (which is the whole purpose of a ref).
If you absolutely need the value to be used on the component's first render, you can do this:
const calculatedConstant = useRef(null);
if (!calculatedConstant.current) {
calculatedConstant.current = calculateConstantFactory();
}
// use the value in calculatedConstant.current;
This one will block your component from rendering before the value is set up.
If you don't want rendering to be blocked, you need useState together with useEffect:
const [calculated, setCalculated] = useState();
useEffect(() => {
setCalculated(calculateConstantFactory())
}, [])
// use the value in calculated
Basically if you need the component to re-render itself: use state. If that's not necessary, use a ref.

Resources