How can I update multiple states at a time with useState hook? - javascript

In functional components, we use useState as opposed to this.setState for classComponents. But as far as I know, you can only set one state at a time with useState, while setState lets you set multiple states at a time, (e.g. this.setState({a1: true, a2: false})).
Does this mean if you wanted to set two states simultaneously with useState, you'd get a double re-render, which is ineffficient? Is there a way to get around this?

AFAIK that's right but you can also think that in most cases you just group states in the way they are correlated instead of having a big this.state with bunch of properties without being related with each other, that also sucks in some way
So commonly you won't be updating two states every time and if you do that, most probably you want to mix those states into one.
So supposing you have these two states:
const [state1, setState1] = useState(...);
const [state2, setState2] = useState(...);
And you always in your onChange function (or whatever) do:
const onChange = () => {
setState1(...);
setState2(...);
}
You should mix those into one state like this:
const [state, setState] = useState({ state1: ..., state: ...}); // or it could be other thing than an object...

Related

Assigning value at useState() declaration vs useEffect()

I have been using these two ways interchangeably however I am not sure which one is the more correct. their behavior seems to be the same but I am sure there is a use case for each. Anyone can help me understand what's the proper use for each case?
const [customer, setCustomers] = useState(props.location.state);
useEffect(() => {
setCustomers(props.location.state);
}, []);
You should normally stick to the first one. Calling the setter of useState may lead to undesired re-renders and decreased performance.
In the first block the customer is initialised directly and no re-render happens. The setCustomer method will change the state and rerender the component. In the end the whole function will run twice which you can verify with a console.log.
const [customer, setCustomers] = useState(0);
useEffect(() => {
setCustomers(15);
}, []);
console.log(customer) // will first output 0 and then 15
Assuming in the second case, you have this as your useState statement:
const [customer, setCustomers] = useState();
The second one sets the value of customer on componentDidMount. So in the initial render, you will not have the appropriate value in your customer variable.
But yes, very soon after that the correct value will be set because of the code written in useEffect.
To clear it up, there will be 2 renders here (because the state variable value changes). In the first one, that won't be the case since the state variable has only one value from beginning.
The first one is more effective.
const [customer, setCustomers] = useState(props.location.state);
If you use second one (by using useEffect), your component will be re-rendered again.
That's, your state variable customer will be updated in useEffect after DOM is initially rendered, this leads the 2nd re-render of the component.
But if you want customer to be updated by props.location.state, you need to add useEffect hook like the following.
useEffect(()=> {
setCustomers(props.location.state);
}, [props.location.state]);
Setting the state's default value upon declaring it is probably the more correct way to go, since it does not trigger a re-render.
Every time you call a setState your component will be re-rendered, so when you do so in the useEffect, you will trigger an unnecessary re-render upon the component mounting, which could be avoided by doing the good ol'
const [value, setValue] = useState(props.location.state)
While of course there are exceptions and many different use cases, setting an initial state in a useEffect is more useful, for example, when you have values you'd expect to change regardless of your component (for example from an external asynchronous API call):
const [value, setValue] = useState(valueExpectedToChange)
useEffect(() => {
setValue(valueExpectedToChange) // will trigger the rerender only when valueExpectedToChange changes
}, [valueExpectedToChange])

What are the pros and cons on using React Context API with the useReducer() hook?

I'm working on a web application and I'm using the React Context without using the useReducer() hook. This is a simple example of how I'm using the Context in my app:
const [stateValue, setStateValue] = useState("");
const [stateValue1, setStateValue1] = useState("");
const contextValue : MainContext = {
stateValue: stateValue,
setStateValue: setStateValue,
stateValue1: stateValue1,
setStateValue1: setStateValue1
}
So I pass to my Context Provider the contextValue and every time a child component has to change the stateValuex just calls the setStateValuex so that it triggers the re-rendering of the stateValuex inside all the child components.
What would the pros and cons be on using instead the Context with the useReducer() hook?
I'd approach it as two issues: 1) pros/cons of useState vs useReducer 2) pros/cons of props vs context. Then stick those answers together.
useReducer can be useful if you have a complicated state that you want to make sure all your update logic is in one centralized location. useState on the other hand is good for simple state where you don't need that kind of control.
props is the standard way to pass values from one component to its child. If you're passing it a short distances, this is the simplest and best approach. context is useful if you need to pass values a long way down the component tree. If you have a lot of cases where a component receives a prop not for itself, but just so it can forward it to a child, then this may indicate context would be better than props.
const contextValue : MainContext = {
stateValue: stateValue,
setStateValue: setStateValue,
stateValue1: stateValue1,
setStateValue1: setStateValue1
}
P.S: if your context value is an object, don't forget to memoize it. If you don't, you'll be creating a brand new object every time you render, and that will force any components consuming the context to render too
const contextValue: MainContext = useMemo(() => {
return {
stateValue: stateValue,
setStateValue: setStateValue,
stateValue1: stateValue1,
setStateValue1: setStateValue1
}
}, [stateValue, stateValue1])
when you use hooks or custom hooks, the states from them are indivisual.
which means suppose you used useReducer in Component A and B. state from useReducer in A, B is totally different whereas if you use contextAPI the state is same.

Does calling setter of useState hook inside if statement imply BREAKING RULES OF HOOKS?

React docs state: don’t call Hooks inside loops, conditions, or nested functions.
Does calling a hook means just calling useState e.g. const [state, useState] = useState(0)?
What about calling setter in conditionals ?
Is this code breaking rules of hooks ?
const [oneHook, setOneHook] = useState(0)
const [anotherHook, setAnotherHook] = useState(false)
if (something) {
setOneHook(1)
setAnotherHook(true)
} else {
setOneHook(0);
setAnotherHook(false)
}
Thanks !
No, that code example does not break the rules of hooks. Every time the component renders there will be exactly the same number of calls to useState in exactly the same order, so that will be fine.
I do want to point out that setting state right away in the body of the component doesn't make much sense. When the component mounts it will start rendering with the initial values from state, but then before it can finish rendering the state has already changed and it has to start over. But presumably that's just an artifact of the example, and in your real code the if would be inside a useEffect or some other practical code location.
React docs state: don’t call Hooks inside loops, conditions, or nested functions.
Alright,the following code shows the example for the above statement. I had the same issue where i needed to set the state inside of a loop, like following
const [someArray, setArray] = useState([]);
someValue.forEach((value, index) => {
setArray([...someArray, value]) //this should be avoided
});
Above thing i have achieved like this following
var temp = [];
var counter = 0;
someValue.forEach((value, index) => {
temp.push(value);
counter++;
if (counter === someValue.length) {
setArray(temp)
}
});
if you are setting a state inside the loop than each time the component re renders which you do not want to get into.
Is this code breaking rules of hooks
No Your code looks fine, as you are setting up the state only based on condition for only once when the component renders

Using hooks with object or single value?

I can't find answer so, this is my question. What is better practice using hooks like that with spread operator?
const [user, setUser] = useState({name: 'John', email: 'john#john.pl'})
setUser(prev => {...prev, name: 'New name'})
Or making state per properties?
const [name, setName] = useState('John')
setName('New name')
const [email, setEmail] = useState('john#john.pl')
setEmail('New email')
What is better option and why?
Clearly the 2nd one since you don't need to pass around the whole user state on each state update but just the user's name or email. Try to keep it simple where ever you can.
please read this info in react docs.
https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables
The both approaches has pros and cons.
Some important hightligts from docs:
we recommend to split state into multiple state variables based on
which values tend to change together.
Both putting all state in a single useState call, and having a
useState call per each field can work. Components tend to be most
readable when you find a balance between these two extremes, and group
related state into a few independent state variables. If the state
logic becomes complex, we recommend managing it with a reducer or a
custom Hook.
Usually it's better to have simple state when using hooks, since setState() works a bit different than this.setState() in class components - it not merges changes, but just update those:
// in class component
this.setState({ name: 'Hello' }); // update only name field of state
// in functional component
setState({ name: 'Hello' }); // sets { name: 'Hello' } as new state
For complex state you can use useReducer() hook.
hooks state easy to use than setState. You can use hooks with spread operator like this
const [user, setUser] = useState({name: 'John', email: 'john#john.pl'});
setUser({...user, name: 'New name'});

Gatsby: Context update causes infinite render loop

I am trying to update context once a Gatsby page loads.
The way I did it, the context is provided to all pages, and once the page loads the context is updated (done with useEffect to ensure it only happens when the component mounts).
Unfortunately, this causes an infinite render loop (perhaps not in Firefox, but at least in Chrome).
Why does this happen? I mean, the context update means all the components below the provider are re-rendered, but the useEffect should only run once, and thats when the component mounts.
Here is the code: https://codesandbox.io/s/6l3337447n
The infinite loop happens when you go to page two (link at bottom of page one).
What is the solution here, if I want to update the context whenever a page loads?
The correct answer for this issue is not to pass an empty dependency array to useEffect but to wrap your context's mergeData in a useCallback hook. I'm unable to edit your code but you may also need to add dependencies to your useCallback like in my example below
import React, { useState, useCallback } from "react"
const defaultContextValue = {
data: {
// set initial data shape here
menuOpen: false,
},
mergeData: () => {},
}
const Context = React.createContext(defaultContextValue)
const { Provider } = Context
function ContextProviderComponent({ children }) {
const [data, setData] = useState({
...defaultContextValue,
mergeData, // shorthand method name
})
const mergeData = useCallback((newData) {
setData(oldData => ({
...oldData,
data: {
...oldData.data,
...newData,
},
}))
}, [setData])
return <Provider value={data}>{children}</Provider>
}
export { Context as default, ContextProviderComponent }
The selected answer is incorrect because the react docs explicitly say not to omit dependencies that are used within the effect which the current selected answer is suggesting.
If you use es-lint with the eslint-plugin-react-hooks it will tell you this is incorrect.
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.
https://reactjs.org/docs/hooks-effect.html
Is it safe to omit functions from the list of dependencies? Generally
speaking, no. It’s difficult to remember which props or state are used
by functions outside of the effect. This is why usually you’ll want to
declare functions needed by an effect inside of it. Then it’s easy to
see what values from the component scope that effect depends on:
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
By default, useEffect runs every render. In your example, useEffect updates the context every render, thus trigger an infinite loop.
There's this bit in the React doc:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
So applies to your example:
useEffect(() => {
console.log("CONTEXT DATA WHEN PAGE 2 LOADS:", data)
mergeData({
location,
})
- }, [location, mergeData, data])
+ }, [])
This way, useEffect only runs on first mount. I think you can also leave location in there, it will also prevent the infinite loop since useEffect doesn't depend on the value from context.

Categories