What's the idea behind useState functionality in ReactJS? - 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.

Related

setState() hook - setting state variable directly?

I was under the impression that a second "setter" method param was needed for setting variables to state with the useState() hook. For example, in the following line of code, "widgets" would be used to get widgets from local state but setWidgets() would be needed to set widgets to state:
const [widgets, setWidgets] = useState(new Map<string, JSX.Element>());
However, I inadvertently wrote code to set widgets directly to state via the widgets state variable:
widgets.set(...)
I confirmed that this approach is successfully setting widgets to local state. Was the second param required in earlier versions of React hooks and the useState() hook has been simplified to support a single parameter as described above? If so then do you know in which version of React this was introduced? Or am I possibly doing something in my code that is only making me think that the 1-param useState is valid?
Maps, Sets and other complex objects can be updated, but you won't trigger rerenders by calling they're native methods, as the object reference itself would be the same. The overall effect of this becomes evident on these type of state values being passed down through props or context.
The proper way of handling setting state on this type of object is something like this:
setWidgets(prev => {
prev.set(id, value);
return new Map(prev);
})
This ensures that the object reference is updated so that changes are properly propagated.
widgets.set(...) is wrong. It might seem to work depending on how it is used, but the state should always be updated immutably using the second element of the array returned by useState (setWidgets).
You can update the map immutably like this:
setWidgets(prevMap => new Map(prevMap.set(key, value)))
BTW storing JSX as part of the state is bad practice.

React useContext with "setState"

I recently started using the Context React Api with useContext Hook.
I have observed that when we have a state variable i.e. const someState = useState(state, setState), some
developers pass setSate directly in the provider values and then calling it in children components.
Is that a good practice?
When you do not use context you have to create a handler to "access" setState in a child component.
I am still using handler functions and pass them into the provider values, to import them from context
in children.
Is passing setState in context a good pracice? I still have some doubts since normally you cannot pass setState directly into a component.
Is there any difference in performance or any other drawbacks I should be considering?
Thank you.
Edit: I actually think I got you wrong, but I'm not sure. My reply is valid for the case if you write your own provider that has a state. If you just use a default provider that provides a setter, I would agree with the reply of Amel.
I personally wouldn't do it, but that's more of an opionion. However, like always, it pretty much depends on what goal you want to reach.
A positive aspect of doing it is, that state setters given by useState always stay the same for each rerender. If you pass a custom value, you should avoid that it changes too often because every component listening to the change using useContext would rerender.
I would still prefer to pass a custom object (e.g. coming from a useMemo to avoid unnecessary rerenders) with a callback to set the state. It's easier to extend if you want to provide more stuff in the future.
Something like this (very simplistic example, that of course doesn't make sense like this, it's just for comprehension):
function MyProvider({children}) {
const [state, setState] = useState(0);
const provided = useMemo(() => ({
value: state,
setValue: (value) => setState(value)
}, [value]);
return <MyContext.Provider value={provided}>{children}</MyContext.Provider>;
}
It would be easier to extend without changing code everyhwere where the context is used. However, I still think there is nothing particular bad in passing just the setter, if that is what you want to achive.
If I understood correctly the difference is that in one way the state is set from the parent component and in the other the state is set from the child component.
Sometimes people do it that way to avoid loop rendering with changing the state. There should be no drawbacks, but using handler functions is the regular way to go.
you can use state as variable not as spreaded one
const state = useContext(0);
state[0] //it's the getter for state you can access the values from this
(state[1])(10) //it is setter for state you can set values with is
(state[1])((oldvalues)=>{//write you code here})

What are the advantages of useRef() instead of just declaring a variable?

Looking at the hooks docs and some blogs we understand that when using useRef() we have a mutable value stored.
Docs:
You might be familiar with refs primarily as a way to access the DOM. If you pass a ref object to React with , React will set its .current property to the corresponding DOM node whenever that node changes.
However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.
This works because useRef() creates a plain JavaScript object. The only difference between useRef() and creating a {current: ...} object yourself is that useRef will give you the same ref object on every render.
What advantages does this give us over just declaring and using a variable with let?
Code ex:
import React, {useRef} from "react";
const MyFunctionalComponent = (props) => {
const refVariable = useRef('value');
//versus
let letVariable = 'value';
}
Follow up:
The answers given were helpful, and combined with some testing gave me the understanding I needed. If anyone comes across this having trouble with the concept, the way I understand it now is:
You can have instance variables, but they are really instant, and every re-render re-initializes them.
useRef() gives you something more permanent, like useState() but updating doesn't trigger re-render, very useful if you are doing a lot of manipulation in a chaining fashion, but wouldn't want to trigger a re-render until the end
useState() should only be tied to variables used by a UI element, as any change to state will trigger re-render of the whole component. Do not have a chain of actions that manipulate state along the way, use refs until the end of the chain.
The last sentence describes it clearly:
useRef will give you the same ref object on every render.
If you declare a simple javascript variable yourself, it will be updated on each render. Refs are used when you need to persist some value during re-renders (Besides using the ref attribute for DOM node reference)
I'm changing my answer and referring people to Arman's below, since it's the more apt one. In essence, for functional components the entire function gets run every time it re-renders. Which means variables that are initialized with a simple let x = 5; in the body of the function will get re-initialized every render, resetting them. That's the reason we need hooks like useRef, it gives a reference to a value that persists between renders

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.

Exactly when does a component update itself?

As far as I know, when a new prop is received, a component is updated based on shouldComponentUpdate function. I have the following container:
I am using redux, and I have the following inside my container.
function filterQuestions(allQuestions, answeredQuestions) {
var filtered = _.reject(allQuestions, (q) => _.contains(answeredQuestions, q.question.id))
return [...filtered]
}
const mapStateToProps = (state) => {
return {
questions: filterQuestions(state.questions.questions, state.questions.answeredQuestions),
}
}
My intention is that the connected component only updates when the output of filterQuestions(...) changes. However, the component updates everytime when there's a change in the global store. If I get rid of filterQuestions and simply pass state.questions, it shows the expected behavior.
I assume there's something wrong with the usage of filterQuestions but I am not sure exactly what is the issue. Can someone pinpoint it for me?
The React Redux package does a shallow equality check whenever it calls your mapStateToProps function, comparing the current returned object against the previous returned object. If the two objects' contents are shallow equal, it doesn't actually re-render your "real" component.
Whenever you use array functions like map() or filter(), you're returning a new array reference. Even if the two arrays have the exact same contents, the objects that have those arrays as fields will no longer be shallow equal, and so React Redux will re-render your component.
There's two main ways to handle this: either use memoized selector functions (such as those provided by the Reselect package) so that you only re-run the filtering when the inputs have actually changed, or implement shouldComponentUpdate on your real component and implement smarter / more complex comparison logic, such as using _.isEqual().
The Redux FAQ also covers this topic: http://redux.js.org/docs/FAQ.html#react-rendering-too-often

Resources