setState() hook - setting state variable directly? - reactjs

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.

Related

Should a function always be used for a setState that gets passed an array or object?

Wanting to improve my understanding of React in functional components I've seen some that pass a seState with a function when spreading an array or object. When I reference the docs I see:
Unlike the setState method found in class components, useState does
not automatically merge update objects. You can replicate this
behavior by combining the function updater form with object spread
syntax
So I'm trying to understand why this implementation:
const [count, setCount] = useState([initialCount]);
setCount([...count, newCount])
is not the preferred when it comes to arrays and objects and this:
const [count, setCount] = useState([initialCount]);
setCount(prevCount => [...prevCount, newCount])
is the preferred. In my research for an answer I haven't found an answer about this and I've read through:
What is the equivalent of passing an updater to setState that takes (state, props) as an argument to update state, using React Hook?
How to use callback with useState hook in react
setState with spread operator
The only conclusions I can establish the need for the function is:
possibly due to the way the object or array is stored in memory
size of the data being passed
Why when it comes to arrays and objects in a useState it should be passed with a function opposed to what's commonly used?
Its probably best practice to pass a function to the useState dispatch to avoid renders getting out of sync, especially if you use asynchronous methods to update state, but also any time your new state is dependent on previous state (so like even incrementing numbers, not just for objects and arrays).
As Kent C. Dodds illustrates, there are issues as it comes to closures and the timing of rendering and function calls when the new state update is dependent upon previous state.
If you want to merge your data with previous state, you want to be sure to merge with the latest state (as of that particular merge call). The setState dispatch provides the previous state as the first parameter. You can be certain that this is the latest state (for that merge) and you won't get out of sync data, or out of order updates.

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.

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 can be a dependency for React hooks?

Looking at the docs, there is a note at the bottom of this section [edited link] that makes it seem like only props or state should be used in a hook dependency list. However, when a "complex expression" on props or state is used in the list, the eslint plugin gives the following:
React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked. eslint(react-hooks/exhaustive-deps)
This has me thinking about what is allowed to be used as a dependency. Can we use a local variable that is calculated from props? Do we need to create some sort of new state variable or ref in this case? I'm not certain if hooks are executed in place within the component (so local variables are available) or are hoisted out of the context of the render (so we have to use only state, props, or other hook values like refs/memos).
Examples
A component has a prop, data, that is an object.
data: {
name: 'name',
id: 2
}
1) It looks like data.name can be used in the dependencies. But can we use a local variable that is set to the property?
const { name } = data;
useEffect(fn, [name]);
2) Would we be able to use a variable that is calculated by a prop in a dependency array?
const isOdd = Boolean(data.id % 2);
useEffect(fn, [isOdd]);
Both cases seem to work with small tests. I'm not knowledgable enough with Hooks to know if it's breaking some rules that would leave the results as indeterminate.
Optimizing Performance by Skipping Effects may help understanding hook dependencies.
Note
If you use this optimization, make sure the array includes all values
from the component scope (such as props and state) that change over
time and that are used by the effect. Otherwise, your code will
reference stale values from previous renders. Learn more about how to
deal with functions and what to do when the array changes too often.
The important bit, "...all values from the component scope...", meaning any value within the component scope used within the hook.
Q: Can we use a local variable that is calculated from props?
Yes
Q: Do we need to create some sort of new state variable or ref in this case?
No
Q: I'm not certain if hooks are executed in place within the component (so local variables are available) or are hoisted out of the context of the render (so we have to use only state, props, or other hook values like refs/memos)
AFAIK, they are just functions with special rules within react. They are invoked within the scope of the functional component.

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

Resources