React useState - update all object values to true - javascript

I have defined an object as follows in a file:
export const items = {
first: false,
second: false,
third: false
}
I'm using it in a component as follows:
import { items } from 'file';
const [elements, setElements] = useState(items);
I have a method which gets called when a button is clicked - the method should change the all the values in elements to true
Using the following changes the values but it does not trigger a re-render of the component (which is what I need)
Object.keys(elements).forEach(element => elements[element] = true);
How can I use setElements to update all the values in elements?

The problem you are facing is that you are mutating the state object, which means that at the end, prevState === nextState and React bails out of rendering. An option is to use a new object and copy the props like this, using the same combo of Object.keys and forEach but adding an extra step:
setState(prevState => {
const nextState = {}
Object.keys(prevState).forEach(key => {
nextState[key] = true
})
return nextState
})

Related

React state is updating but the component is not

There is a component that maps through an array stored in the state. A button, when it is clicked it updates the state, this action is working.
The problem is that the component is not updating too.
Here is the code:
const MyComponent = () => {
...
const [fields, setFields] = useState([{value: 'test', editable: false},
{value: 'test2', editable: false}]);
...
const toggleClass = (id) => {
const aux = fields;
aux[id].editable = true;
setFields(aux);
}
...
return (
<div>
...
{fields.map((field, id) => {
return (
<div>
<input className={field.editable ? 'class1' : 'class2'} />
<button onClick={() => toggleClass(id)}>click</button>
</div>
);
})}
</div>
);
I put logs and the state (fields) is updated after click to editable = true. But the css class is not changing.
Is there any solution to this issue?
You need to make a copy of your existing state array, otherwise you're mutating state which is a bad practice.
const toggleClass = id => {
const aux = [...fields]; //here we spread in order to take a copy
aux[id].editable = true; //mutate the copy
setFields(aux); //set the copy as the new state
};
That's happening because you are mutating the value of fields, which makes it unsure for React to decide whether to update the component or not. Ideally if you should be providing a new object to the setFields.
So, your toggleClass function should look like something below:
const toggleClass = (id) => {
const aux = [...fields]; //This gives a new array as a copy of fields state
aux[id].editable = !aux[id].editable;
setFields(aux);
}
BTW, I also noticed that you're not assigning a key prop to each div of the the map output. Its a good practice to provide key prop, and ideally keep away from using the index as the key.

Cannot read property 'length' of undefined in functional component while it works in class component [duplicate]

I'm finding these two pieces of the React Hooks docs a little confusing. Which one is the best practice for updating a state object using the state hook?
Imagine a want to make the following state update:
INITIAL_STATE = {
propA: true,
propB: true
}
stateAfter = {
propA: true,
propB: false // Changing this property
}
OPTION 1
From the Using the React Hook article, we get that this is possible:
const [count, setCount] = useState(0);
setCount(count + 1);
So I could do:
const [myState, setMyState] = useState(INITIAL_STATE);
And then:
setMyState({
...myState,
propB: false
});
OPTION 2
And from the Hooks Reference we get that:
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:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
As far as I know, both works. So, what is the difference? Which one is the best practice? Should I use pass the function (OPTION 2) to access the previous state, or should I simply access the current state with spread syntax (OPTION 1)?
Both options are valid, but just as with setState in a class component you need to be careful when updating state derived from something that already is in state.
If you e.g. update a count twice in a row, it will not work as expected if you don't use the function version of updating the state.
const { useState } = React;
function App() {
const [count, setCount] = useState(0);
function brokenIncrement() {
setCount(count + 1);
setCount(count + 1);
}
function increment() {
setCount(count => count + 1);
setCount(count => count + 1);
}
return (
<div>
<div>{count}</div>
<button onClick={brokenIncrement}>Broken increment</button>
<button onClick={increment}>Increment</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
If anyone is searching for useState() hooks update for object
Through Input
const [state, setState] = useState({ fName: "", lName: "" });
const handleChange = e => {
const { name, value } = e.target;
setState(prevState => ({
...prevState,
[name]: value
}));
};
<input
value={state.fName}
type="text"
onChange={handleChange}
name="fName"
/>
<input
value={state.lName}
type="text"
onChange={handleChange}
name="lName"
/>
Through onSubmit or button click
setState(prevState => ({
...prevState,
fName: 'your updated value here'
}));
The best practice is to use separate calls:
const [a, setA] = useState(true);
const [b, setB] = useState(true);
Option 1 might lead to more bugs because such code often end up inside a closure which has an outdated value of myState.
Option 2 should be used when the new state is based on the old one:
setCount(count => count + 1);
For complex state structure consider using useReducer
For complex structures that share some shape and logic you can create a custom hook:
function useField(defaultValue) {
const [value, setValue] = useState(defaultValue);
const [dirty, setDirty] = useState(false);
const [touched, setTouched] = useState(false);
function handleChange(e) {
setValue(e.target.value);
setTouched(true);
}
return {
value, setValue,
dirty, setDirty,
touched, setTouched,
handleChange
}
}
function MyComponent() {
const username = useField('some username');
const email = useField('some#mail.com');
return <input name="username" value={username.value} onChange={username.handleChange}/>;
}
Which one is the best practice for updating a state object using the state hook?
They are both valid as other answers have pointed out.
what is the difference?
It seems like the confusion is due to "Unlike the setState method found in class components, useState does not automatically merge update objects", especially the "merge" part.
Let's compare this.setState & useState
class SetStateApp extends React.Component {
state = {
propA: true,
propB: true
};
toggle = e => {
const { name } = e.target;
this.setState(
prevState => ({
[name]: !prevState[name]
}),
() => console.log(`this.state`, this.state)
);
};
...
}
function HooksApp() {
const INITIAL_STATE = { propA: true, propB: true };
const [myState, setMyState] = React.useState(INITIAL_STATE);
const { propA, propB } = myState;
function toggle(e) {
const { name } = e.target;
setMyState({ [name]: !myState[name] });
}
...
}
Both of them toggles propA/B in toggle handler.
And they both update just one prop passed as e.target.name.
Check out the difference it makes when you update just one property in setMyState.
Following demo shows that clicking on propA throws an error(which occurs setMyState only),
You can following along
Warning: A component is changing a controlled input of type checkbox to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
It's because when you click on propA checkbox, propB value is dropped and only propA value is toggled thus making propB's checked value as undefined making the checkbox uncontrolled.
And the this.setState updates only one property at a time but it merges other property thus the checkboxes stay controlled.
I dug thru the source code and the behavior is due to useState calling useReducer
Internally, useState calls useReducer, which returns whatever state a reducer returns.
https://github.com/facebook/react/blob/2b93d686e3/packages/react-reconciler/src/ReactFiberHooks.js#L1230
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
...
try {
return updateState(initialState);
} finally {
...
}
},
where updateState is the internal implementation for useReducer.
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateReducer(reducer, initialArg, init);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
If you are familiar with Redux, you normally return a new object by spreading over previous state as you did in option 1.
setMyState({
...myState,
propB: false
});
So if you set just one property, other properties are not merged.
One or more options regarding state type can be suitable depending on your usecase
Generally you could follow the following rules to decide the sort of state that you want
First: Are the individual states related
If the individual state that you have in your application are related to one other then you can choose to group them together in an object. Else its better to keep them separate and use multiple useState so that when dealing with specific handlers you are only updating the relavant state property and are not concerned about the others
For instance, user properties such as name, email are related and you can group them together Whereas for maintaining multiple counters you can make use of multiple useState hooks
Second: Is the logic to update state complex and depends on the handler or user interaction
In the above case its better to make use of useReducer for state definition. Such kind of scenario is very common when you are trying to create for example and todo app where you want to update, create and delete elements on different interactions
Should I use pass the function (OPTION 2) to access the previous
state, or should I simply access the current state with spread syntax
(OPTION 1)?
state updates using hooks are also batched and hence whenever you want to update state based on previous one its better to use the callback pattern.
The callback pattern to update state also comes in handy when the setter doesn't receive updated value from enclosed closure due to it being defined only once. An example of such as case if the useEffect being called only on initial render when adds a listener that updates state on an event.
Both are perfectly fine for that use case. The functional argument that you pass to setState is only really useful when you want to conditionally set the state by diffing the previous state (I mean you can just do it with logic surrounding the call to setState but I think it looks cleaner in the function) or if you set state in a closure that doesn't have immediate access to the freshest version of previous state.
An example being something like an event listener that is only bound once (for whatever reason) on mount to the window. E.g.
useEffect(function() {
window.addEventListener("click", handleClick)
}, [])
function handleClick() {
setState(prevState => ({...prevState, new: true }))
}
If handleClick was only setting the state using option 1, it would look like setState({...prevState, new: true }). However, this would likely introduce a bug because prevState would only capture the state on initial render and not from any updates. The function argument passed to setState would always have access to the most recent iteration of your state.
Both options are valid but they do make a difference.
Use Option 1 (setCount(count + 1)) if
Property doesn't matter visually when it updates browser
Sacrifice refresh rate for performance
Updating input state based on event (ie event.target.value); if you use Option 2, it will set event to null due to performance reasons unless you have event.persist() - Refer to event pooling.
Use Option 2 (setCount(c => c + 1)) if
Property does matter when it updates on the browser
Sacrifice performance for better refresh rate
I noticed this issue when some Alerts with autoclose feature that should close sequentially closed in batches.
Note: I don't have stats proving the difference in performance but its based on a React conference on React 16 performance optimizations.
I find it very convenient to use useReducer hook for managing complex state, instead of useState. You initialize state and updating function like this:
const initialState = { name: "Bob", occupation: "builder" };
const [state, updateState] = useReducer(
(state, updates) => {...state, ...updates},
initialState
);
And then you're able to update your state by only passing partial updates:
updateState({ occupation: "postman" })
The solution I am going to propose is much simpler and easier to not mess up than the ones above, and has the same usage as the useState API.
Use the npm package use-merge-state (here). Add it to your dependencies, then, use it like:
const useMergeState = require("use-merge-state") // Import
const [state, setState] = useMergeState(initial_state, {merge: true}) // Declare
setState(new_state) // Just like you set a new state with 'useState'
Hope this helps everyone. :)

Iterate through getter and setter react state hooks

In a react component that uses state hooks to expose several state properties, is there a way to iterate through all of the state properties and potentially change them? The issue is that I have lots of state properties, so I'd rather not hard-code all the getters and setters to iterate over the state properties.
In this example, let's say that all of my state properties have a default of 0, and if they are different, I'd like to do something. How do I loop over the state properties?
const exampleComponent = () => {
const [prop1, setProp1] = React.useState(0);
const [prop2, setProp2] = React.useState(0);
const [prop3, setProp3] = React.useState(0);
//...etc., lots of properties
// Loop over the properties. How should this loop be written?
Object.keys(this.state).map(function (key) {
// do something with each key-value pair here
});
An alternative is to assign the states that you want into an array and then destructure them into named constants (if required) and enumerate the states array. See example below:
const exampleComponent = () => {
const states = [React.useState(0), React.useState(0), React.useState(0)];
const [
[prop1, setProp1],
[prop2, setProp2],
[prop3, setProp3],
] = states;
// Loop over the properties.
states.forEach(([state, setState]) => {
// do something with each key-value pair here
});
}
If you need to loop over the properties, I'd use an array for state instead:
const [numArr, setNumArr] = useState([0, 0, 0]);
// ...
numArr.forEach((num, i) => {
// do something with each key-value pair here
});
If you have lots of states that are related to each other, then instead of having each state separately, you might be better off using the useReducer hook.
EDIT:
Apologies, I should have mentioned this earlier that handling state with useReducer hook can be a bit verbose and may be complex if one is not familiar with it.
Here is an example, where instead of having three separate states, we have one state object with three properties, when UPDATE_ACTION1 is dispatched, the code loops over the properties and all the relevant ones are incremented by 2.
//define some actions
const UPDATE_ACTION1 = "UPDATE_ACTION1";
const UPDATE_ACTION2 = "UPDATE_ACTION2";
//define a reducer function that will update the state
const objReducer = (state, action) => {
switch (action.type) {
case UPDATE_ACTION1:
const keys = Object.keys(state);
const newState = {};
keys.forEach(key => {
//perform any function on each property/key
//here we just increment the value of each property by the given value
if (key !== "isValid") {
newState[key] = state[key] + action.value;
}
});
return newState;
case UPDATE_ACTION2:
//do something else e.g. check validity and return updated state
return { ...state, isValid: true };
default:
return state;
}
};
//inside the component: call useReducer and pass it the reducer function and an initial state
//it will return the current state and a dispatch function
const [objState, dispatch] = useReducer(objReducer, {
prop1: 0,
prop2: 0,
prop3: 0
});
//somewhere in your code, dispatch the action. it will update the state depending upon the action.
const somethingHappens = () => {
//some other operations are performed here
dispatch({ type: UPDATE_ACTION1, value: 2 });
};

UseState called by UseEffect doesn't update the variable using the Set method

Consider the code :
import React, { useState, useEffect } from 'react';
........ More stuff
const ProductContext = React.createContext();
const ProductConsumer = ProductContext.Consumer;
const ProductProvider = ({ children }) => {
const [state, setState] = useState({
sideBarOpen: false,
cartOpen: true,
cartItems: 10,
links: linkData,
socialIcons: socialData,
cart: [],
cartSubTotal: 0,
cartTax: 0,
cartTotal: 0,
.......
loading: true,
cartCounter: 0,
});
const getTotals = () => {
// .. Do some calculations ....
return {
cartItems,
subTotal,
tax,
total,
};
};
const addTotals = () => {
const totals = getTotals();
setState({
...state,
cartItems: totals.cartItems,
cartSubTotal: totals.subTotal,
cartTax: totals.tax,
cartTotal: totals.total,
});
};
/**
* Use Effect only when cart has been changed
*/
useEffect(() => {
if (state.cartCounter > 0) {
addTotals();
syncStorage();
openCart();
}
}, [state.cartCounter]);
..... More code
return (
<ProductContext.Provider
value={{
...state,
............... More stuff
}}
>
{children}
</ProductContext.Provider>
);
};
export { ProductProvider, ProductConsumer };
This is a Context of a Shopping cart ,whenever the user add a new item to the cart
this piece of code runs :
useEffect(() => {
if (state.cartCounter > 0) {
addTotals();
syncStorage();
openCart();
}
}, [state.cartCounter]);
And updates the state , however the setState function doesn't update state
when running :
setState({
...state,
cartItems: totals.cartItems,
cartSubTotal: totals.subTotal,
cartTax: totals.tax,
cartTotal: totals.total,
});
Inside addTotals , even though this function is being called automatically when UseEffect detects that state.cartCounter has been changed.
Why aren't the changes being reflected in the state variable ?
Without a stripped down working example, I can only guess at the problems...
Potential Problem 1
You're calling a callback function in useEffect which should be added to it's [dependencies] for memoization.
const dep2 = React.useCallback(() => {}, []);
useEffect(() => {
if(dep1 > 0) {
dep2();
}
}, [dep1, dep2]);
Since dep2 is a callback function, if it's not wrapped in a React.useCallback, then it could potentially cause an infinite re-render if it's changed.
Potential Problem 2
You're mutating the state object or one of its properties. Since I'm not seeing the full code, this is only an assumption. But Array methods like: splice, push, unshift, shift, pop, sort to name a few cause mutations to the original Array. In addition, objects can be mutated by using delete prop or obj.name = "example" or obj["total"] = 2. Again, without the full code, it's just a guess.
Potential Problem 3
You're attempting to spread stale state when it's executed. When using multiple setState calls to update an object, there's no guarantee that the state is going to be up-to-date when it's executed. Best practice is to pass setState a function which accepts the current state as an argument and returns an updated state object:
setState(prevState => ({
...prevState,
prop1: prevState.prop1 + 1
}));
This ensures the state is always up-to-date when it's being batch executed. For example, if the first setState updates cartTotal: 11, then prevState.cartTotal is guaranteed to be 11 when the next setState is executed.
Potential Problem 4
If state.cartCounter is ever updated within this component, then this will cause an infinite re-render loop because the useEffect listens and fires every time it changes. This may or may not be a problem within your project, but it's something to be aware of. A workaround is to trigger a boolean to prevent addTotals from executing more than once. Since the prop name "cartCounter" is a number and is rather ambiguous to its overall functionality, then it may not be the best way to update the cart totals synchronously.
React.useEffect(() => {
if (state.cartCounter > 0 && state.updateCart) {
addTotals();
...etc
}
}, [state.updateCart, state.cartCounter, addTotals]);
Working demo (click the Add to Cart button to update cart state):
If neither of the problems mentioned above solves your problem, then I'd recommend creating a mwe. Otherwise, it's a guessing game.

React not mapping certain objects in an array

I have to dynamically render an input form, based on the selection of a radio button. I have an array that is incremented every time the user select an radio button.
The problem is: I append an object to the array, and try to map that array on render() function. The map apparently is ignoring the object that I insert.
The select radio button code:
<MDBInput
onClick={() => {
let dependentFullName = dependent.dependentFullName;
let dependentAnswerList = this.state[dependentFullName];
let newQuestionData = {
question: thing.pergunta,
answer: true,
answerRaised: true,
info: ''
};
dependentAnswerList[thing.pergunta] = newQuestionData;
this.setState({
[dependentFullName]: dependentAnswerList
})
}}
checked={this.state[dependent.dependentFullName][thing.pergunta] ? this.state[dependent.dependentFullName][thing.pergunta]["answer"] ? true : false : false}
label='Sim'
type='radio'
id={"holder." + thing.pergunta}
/>
The map rendering code prototype:
{this.state.holder.map((question) => (<React.Fragment>
<h5>{question}</h5> <h5>{question.question}</h5></React.Fragment>))}
Try setting a state with a new reference of your mutated array:
this.setState({
[dependentFullName]: [...dependentAnswerList]
});
On state change, react makes shallow comparison with the previous one, in your case it has the same reference, therefore no render triggered.
What does setState do?.
setState() schedules an update to a component’s state object. When state changes, the component responds by re-rendering.
From setState API:
Both state and props received by the updater function are guaranteed to be up-to-date. The output of the updater is shallowly merged with the state.
A possible full-fix may look like:
const onClick = () => {
const dependentFullName = dependent.dependentFullName;
const dependentAnswerList = this.state[dependentFullName];
const newQuestionData = {
question: thing.pergunta,
answer: true,
answerRaised: true,
info: ''
};
this.setState({
[dependentFullName]: {
...dependentAnswerList,
[thing.pergunta]: newQuestionData
}
});
};
<MDBInput onClick={onClick} {...rest}/>;

Categories

Resources