Toggle item in an array react - javascript

I want to toggle a property of an object in an array. The array looks as follows. This is being used in a react component and When a user clicks on a button I want to toggle the winner.
const initialFixtures = [{
teams: {
home: 'Liverpool',
away: 'Manchester Utd'
},
winner: 'Liverpool'
},
{
teams: {
home: 'Chelsea',
away: 'Fulham'
},
winner: 'Fulham'
}, ,
{
teams: {
home: 'Arsenal',
away: 'Tottenham'
},
winner: 'Arsenal'
}
];
My react code looks something like this
function Parent = () => {
const [fixtures, setUpdateFixtures] = useState(initialFixtures)
const toggleWinner = (index) => {
const updatedFixtures = fixtures.map((fixture, i) => {
if (i === index) {
return {
...fixture,
winner: fixture.winner === home ? away : home,
};
} else {
return fixture;
}
})
setUpdateFixtures(updatedFixtures);
}
return <Fixtures fixtures={fixtures} toggleWinner={toggleWinner} />;
}
function Fixtures = ({ fixtures, toggleWinner }) => {
fixtures.map((fixture, index) => (
<div>
<p>{fixture.winner} </p>
<button onClick = {() => toggleWinner(index)}> Change Winner</button>
</div>
))
}
the code works but it feels like it is a bit too much. I am sure there is a better more succinct way of doing this. Can anyone advise? I do need to pass the fixtures in from the parent of the Fixture component for architectural reasons.

const updatedFixtures = [...fixtures];
const fixture = updatedFixtures[i];
updatedFixtures[i] = {
...fixture,
winner: fixture.winner === fixture.teams.home ? fixture.teams.away : fixture.teams.home,
};

You can slice the fixtures array into three parts:
from 0 to index: fixtures.slice(0, index). This part is moved to the new array intact.
The single item at index. This part/item is thrown away because of being changed and a new item is substituted.
The rest of the array: fixtures.slice(index + 1).
Next, put them into a new array:
const newFixtures = [
...fixtures.slice(0, index), // part 1
{/* new item at 'index' */}, // part 2
...fixtures.slice(index + 1) // part 3
];
To construct the new item:
Using spread operator:
const newFixture = {
...oldFixture,
winner: /* new value */
};
Using Object.assign:
const newFixture = Object.assign({}, oldFixture, {
winner: /* new value */
});

if you write your code in such a way - this will do the job.
const toggleWinner = index => {
const { winner, teams: { home, away } } = fixtures[index];
fixtures[index].winner = winner === home ? away : home;
setUpdateFixtures([...fixtures]);
};
Setting a new array of fixtures to state is completely enough to trigger render on Fixtures component.
I have made a working example for you.

You can use libraries like immer to update nested states easily.
const initialFixtures = [{
teams: {
home: 'Liverpool',
away: 'Manchester Utd'
},
winner: 'Liverpool'
},
{
teams: {
home: 'Chelsea',
away: 'Fulham'
},
winner: 'Fulham'
}, ,
{
teams: {
home: 'Arsenal',
away: 'Tottenham'
},
winner: 'Arsenal'
}
];
const newState = immer.default(initialFixtures, draft => {
draft[1].winner = "something";
});
console.log(newState);
<script src="https://cdn.jsdelivr.net/npm/immer#1.0.1/dist/immer.umd.js"></script>

If you are comfortable to use a class based approach, you can try something like this:
Create a class that holds property value for team.
Create a boolean property in this class, say isHomeWinner. This property will decide the winner.
Then create a getter property winner which will lookup this.isHomeWinner and will give necessary value.
This will enable you to have a clean toggle function: this.isHomeWinner = !this.isHomeWinner.
You can also write your toggleWinner as:
const toggleWinner = (index) => {
const newArr = initialFixtures.slice();
newArr[index].toggle();
return newArr;
};
This looks clean and declarative. Note, if immutability is necessary then only this is required. If you are comfortable with mutating values, just pass fixture.toggle to your react component. You may need to bind context, but that should work as well.
So it would look something like:
function Fixtures = ({ fixtures, toggleWinner }) => {
fixtures.map((fixture, index) => (
<div>
<p>{fixture.winner} </p>
<button onClick = {() => fixture.toggle() }> Change Winner</button>
// or
// <button onClick = { fixture.toggle.bind(fixture) }> Change Winner</button>
</div>
))
}
Following is a sample of class and its use:
class Fixtures {
constructor(home, away, isHomeWinner) {
this.team = {
home,
away
};
this.isHomeWinner = isHomeWinner === undefined ? true : isHomeWinner;
}
get winner() {
return this.isHomeWinner ? this.team.home : this.team.away;
}
toggle() {
this.isHomeWinner = !this.isHomeWinner
}
}
let initialFixtures = [
new Fixtures('Liverpool', 'Manchester Utd'),
new Fixtures('Chelsea', 'Fulham', false),
new Fixtures('Arsenal', 'Tottenham'),
];
const toggleWinner = (index) => {
const newArr = initialFixtures.slice();
newArr[index].toggle();
return newArr;
};
initialFixtures.forEach((fixture) => console.log(fixture.winner))
console.log('----------------')
initialFixtures = toggleWinner(1);
initialFixtures.forEach((fixture) => console.log(fixture.winner))
initialFixtures = toggleWinner(2);
console.log('----------------')
initialFixtures.forEach((fixture) => console.log(fixture.winner))

const toggleWinner = (index) => {
let updatedFixtures = [...fixtures].splice(index, 1, {...fixtures[index],
winner: fixtures[index].winner === fixtures[index].teams.home
? fixtures[index].teams.away : fixtures[index].teams.home})
setUpdateFixtures(updatedFixtures);
}

Related

Class not working when changed to functional component [duplicate]

What is the correct way of updating state, in a nested object, in React with Hooks?
export Example = () => {
const [exampleState, setExampleState] = useState(
{masterField: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b"
fieldTwoTwo: "c"
}
}
})
How would one use setExampleState to update exampleState to a (appending an field)?
const a = {
masterField: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b",
fieldTwoTwo: "c"
}
},
masterField2: {
fieldOne: "c",
fieldTwo: {
fieldTwoOne: "d",
fieldTwoTwo: "e"
}
},
}
}
b (Changing values)?
const b = {masterField: {
fieldOne: "e",
fieldTwo: {
fieldTwoOne: "f"
fieldTwoTwo: "g"
}
}
})
You can pass new value like this:
setExampleState({...exampleState, masterField2: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b",
fieldTwoTwo: "c"
}
},
})
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'
}));
Generally you should watch out for deeply nested objects in React state. To avoid unexpected behavior, the state should be updated immutably. When you have deep objects, you end up deep cloning them for immutability, which can be quite expensive in React. Why?
Once you deep clone the state, React will recalculate and re-render everything that depends on the variables, even though they haven't changed!
So, before trying to solve your issue, think how you can flatten the state first. As soon as you do that, you will find handy tools that will help dealing with large states, such as useReducer().
In case you thought about it, but are still convinced you need to use a deeply nested state tree, you can still use useState() with libraries like immutable.js and Immutability-helper. They make it simple to update or clone deep objects without having to worry about mutability.
I'm late to the party.. :)
#aseferov answer works very well when the intention is to re-enter the entire object structure. However, if the target/goal is to update a specific field value in an Object, I believe the approach below is better.
situation:
const [infoData, setInfoData] = useState({
major: {
name: "John Doe",
age: "24",
sex: "M",
},
minor:{
id: 4,
collegeRegion: "south",
}
});
Updating a specific record will require making a recall to the previous State prevState
Here:
setInfoData((prevState) => ({
...prevState,
major: {
...prevState.major,
name: "Tan Long",
}
}));
perhaps
setInfoData((prevState) => ({
...prevState,
major: {
...prevState.major,
name: "Tan Long",
},
minor: {
...prevState.minor,
collegeRegion: "northEast"
}));
I hope this helps anyone trying to solve a similar problem.
It's possible to use useReducer hook for managing complex state, instead of useState. To do it, first 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 can update the state by only passing partial updates, like this:
updateState({ occupation: "postman" })
In 2022
If you are looking for the same functionality as this.setState ( came from the class components ) in functional components then this is the answer that helps you a lot.
For Example
You have a state like below and want to update the specific field only from the whole state then you need to use the object destructing every time and sometimes it will be irritating.
const [state, setState] = useState({first: 1, second: 2});
// results will be state = {first: 3} instead of {first: 3, second: 2}
setState({first: 3})
// To resolve that you need to use object destructing every time
// results will be state = {first: 3, second: 2}
setState(prev => ({...prev, first: 3}))
To solve that I came up with the useReducer approach. Please check useReducer.
const stateReducer = (state, action) => ({
...state,
...(typeof action === 'function' ? action(state) : action),
});
const [state, setState] = useReducer(stateReducer, {first: 1, second: 2});
// results will be state = {first: 3, second: 2}
setState({first: 3})
// you can also access the previous state callback if you want
// results will remain same, state = {first: 3, second: 2}
setState(prev => ({...prev, first: 3}))
You can store that stateReducer in utils files and import it in every file if you want.
Here is the custom hook if you want.
import React from 'react';
export const stateReducer = (state, action) => ({
...state,
...(typeof action === 'function' ? action(state) : action),
});
const useReducer = (initial, lazyInitializer = null) => {
const [state, setState] = React.useReducer(stateReducer, initial, init =>
lazyInitializer ? lazyInitializer(init) : init
);
return [state, setState];
};
export default useReducer;
Typescript
import React, { Dispatch } from "react";
type SetStateAction<S> = S | ((prev: S) => S);
type STATE<R> = [R, Dispatch<SetStateAction<Partial<R>>>];
const stateReducer = (state, action) => ({
...state,
...(typeof action === "function" ? action(state) : action),
});
const useReducer = <S>(initial, lazyInitializer = null): STATE<S> => {
const [state, setState] = React.useReducer(stateReducer, initial, (init) =>
lazyInitializer ? lazyInitializer(init) : init,
);
return [state, setState];
};
export default useReducer;
Thanks Philip this helped me - my use case was I had a form with lot of input fields so I maintained initial state as object and I was not able to update the object state.The above post helped me :)
const [projectGroupDetails, setProjectGroupDetails] = useState({
"projectGroupId": "",
"projectGroup": "DDD",
"project-id": "",
"appd-ui": "",
"appd-node": ""
});
const inputGroupChangeHandler = (event) => {
setProjectGroupDetails((prevState) => ({
...prevState,
[event.target.id]: event.target.value
}));
}
<Input
id="projectGroupId"
labelText="Project Group Id"
value={projectGroupDetails.projectGroupId}
onChange={inputGroupChangeHandler}
/>
You have to use Rest parameters and spread syntax (https://javascript.info/rest-parameters-spread) AND set a function with preState as the argument of the setState.
Does not work (missing function)
[state, setState] = useState({})
const key = 'foo';
const value = 'bar';
setState({
...state,
[key]: value
});
Does work!
[state, setState] = useState({})
const key = 'foo';
const value = 'bar';
setState(prevState => ({
...prevState,
[key]: value
}));
I have given both Append, Whole object update, Specific key update examples for the solve
Append and modify both can be done by a simple step. I think this is more stable and safe which has no immutable or mutable dependency.
This is how you can append new object
setExampleState(prevState => ({
...prevState,
masterField2: {
fieldOne: "c",
fieldTwo: {
fieldTwoOne: "d",
fieldTwoTwo: "e"
}
},
}))
Suppose you want to modify again the masterField2 object. There can be two situation. You want to update the whole object or update a specific key of the object.
Update the whole object - So here the whole value for key masterField2 will be updated.
setExampleState(prevState => ({
...prevState,
masterField2: {
fieldOne: "c",
fieldTwo: {
fieldTwoOne: "d",
fieldTwoTwo: "e"
}
},
}))
But what if you wanted to change only fieldTwoOne key inside the masterField2 object. You do the following.
let oldMasterField2 = exampleState.masterField2
oldMasterField2.fieldTwo.fieldTwoOne = 'changed';
setExampleState(prevState => ({
...prevState,
masterField2: oldMasterField2
}))
function App() {
const [todos, setTodos] = useState([
{ id: 1, title: "Selectus aut autem", completed: false },
{ id: 2, title: "Luis ut nam facilis et officia qui", completed: false },
{ id: 3, title: "Fugiat veniam minus", completed: false },
{ id: 4, title: "Aet porro tempora", completed: true },
{ id: 5, title: "Laboriosam mollitia et enim quasi", completed: false }
]);
const changeInput = (e) => {todos.map(items => items.id === parseInt(e.target.value) && (items.completed = e.target.checked));
setTodos([...todos], todos);}
return (
<div className="container">
{todos.map(items => {
return (
<div key={items.id}>
<label>
<input type="checkbox"
onChange={changeInput}
value={items.id}
checked={items.completed} /> {items.title}</label>
</div>
)
})}
</div>
);
}
Initially I used object in useState, but then I moved to useReducer hook for complex cases. I felt a performance improvement when I refactored the code.
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.
useReducer React docs
I already implemented such hook for my own use:
/**
* Same as useObjectState but uses useReducer instead of useState
* (better performance for complex cases)
* #param {*} PropsWithDefaultValues object with all needed props
* and their initial value
* #returns [state, setProp] state - the state object, setProp - dispatch
* changes one (given prop name & prop value) or multiple props (given an
* object { prop: value, ...}) in object state
*/
export function useObjectReducer(PropsWithDefaultValues) {
const [state, dispatch] = useReducer(reducer, PropsWithDefaultValues);
//newFieldsVal={[field_name]: [field_value], ...}
function reducer(state, newFieldsVal) {
return { ...state, ...newFieldsVal };
}
return [
state,
(newFieldsVal, newVal) => {
if (typeof newVal !== "undefined") {
const tmp = {};
tmp[newFieldsVal] = newVal;
dispatch(tmp);
} else {
dispatch(newFieldsVal);
}
},
];
}
more related hooks.
, do it like this example :
first creat state of the objects:
const [isSelected, setSelection] = useState([{ id_1: false }, { id_2: false }, { id_3: false }]);
then change the value on of them:
// if the id_1 is false make it true or return it false.
onValueChange={() => isSelected.id_1 == false ? setSelection([{ ...isSelected, id_1: true }]) : setSelection([{ ...isSelected, id_1: false }])}
I think best solution is Immer. It allows you to update object like you are directly modifying fields (masterField.fieldOne.fieldx = 'abc'). But it will not change actual object of course. It collects all updates on a draft object and gives you a final object at the end which you can use to replace original object.
Your Object for which you want to make state
let teams = {
team: [
{
name: "one",
id: "1"
},
]
}
Making State of teams object
const [state, setState] = useState(teams);
Update State like this
setState((prevState)=>({...prevState,team:[
...prevState.team,
{
name: "two",
id: "2"
}
]}))
After the updation State becomes
{
team: [
{
name: "one",
id: "1"
},
{
name: "two",
id: "2"
}
]
}
To Render items according to current state use Map function
{state.team.map((curr_team) => {
return (
<div>
<p>{curr_team.id}</p>
<p>{curr_team.name}</p>
</div>
)
})}
If you work with boolean values and arrays This can help you:
const [checkedOrders, setCheckedOrders] = useState<Record<string, TEntity>>({});
const handleToggleCheck = (entity: TEntity) => {
const _checkedOrders = { ...checkedOrders };
const isChecked = entity.id in checkedOrders;
if (isChecked) {
delete _checkedOrders[entity.id];
} else {
_checkedOrders[entity.id] = entity;
}
setCheckedOrders(_checkedOrders);
};
Answers are already there but This type isn't mentioned so take look at the example of this kind ...
const[data,setdata]= useState({
username: [
email,
"required",
//...some additional codes
],
password: [
password,
"required|password-5",
//..additional code if any..
],
})
**To update the state variable email in this example in the input field you can add similar code with your variable name **
<Input
onChangeText={(t) => setdata(prevState=>({...prevState,username:{[0]:t}}))}
value={data.username[0]}
/>
I leave you a utility function to inmutably update objects
/**
* Inmutable update object
* #param {Object} oldObject Object to update
* #param {Object} updatedValues Object with new values
* #return {Object} New Object with updated values
*/
export const updateObject = (oldObject, updatedValues) => {
return {
...oldObject,
...updatedValues
};
};
So you can use it like this
const MyComponent = props => {
const [orderForm, setOrderForm] = useState({
specialities: {
elementType: "select",
elementConfig: {
options: [],
label: "Specialities"
},
touched: false
}
});
// I want to update the options list, to fill a select element
// ---------- Update with fetched elements ---------- //
const updateSpecialitiesData = data => {
// Inmutably update elementConfig object. i.e label field is not modified
const updatedOptions = updateObject(
orderForm[formElementKey]["elementConfig"],
{
options: data
}
);
// Inmutably update the relevant element.
const updatedFormElement = updateObject(orderForm[formElementKey], {
touched: true,
elementConfig: updatedOptions
});
// Inmutably update the relevant element in the state.
const orderFormUpdated = updateObject(orderForm, {
[formElementKey]: updatedFormElement
});
setOrderForm(orderFormUpdated);
};
useEffect(() => {
// some code to fetch data
updateSpecialitiesData.current("specialities",fetchedData);
}, [updateSpecialitiesData]);
// More component code
}
If not you have more utilities here : https://es.reactjs.org/docs/update.html
I think a more elegant solution will be to create the updated state object, while retaining the previous values of state. The Object property which is needed to be updated can be provided in the form of array something like this -
import React,{useState, useEffect} from 'react'
export default function Home2(props) {
const [x, setX] = useState({name : '',add : {full : '', pin : '', d : { v : '' }}})
const handleClick = (e, type)=>{
let obj = {}
if(type.length > 1){
var z = {}
var z2 = x[type[0]]
type.forEach((val, idx)=>{
if(idx === type.length - 1){
z[val] = e.target.value
}
else if(idx > 0){
Object.assign(z , z2) /*{...z2 , [val]:{} }*/
z[val] = {}
z = z[val]
z2 = z2[val]
}else{
z = {...z2}
obj = z
}
})
}else obj = e.target.value
setX( { ...x , [type[0]] : obj } )
}
return (
<div>
<input value = {x.name} onChange={e=>handleClick(e,["name"])}/>
<input value = {x.add.full} onChange={e=>handleClick(e,["add","full"])} />
<input value = {x.add.pin} onChange={e=>handleClick(e,["add","pin"])} /><br/>
<input value = {x.add.d.v} onChange={e=>handleClick(e,["add","d","v"])} /><br/>
{x.name} <br/>
{x.add.full} <br/>
{x.add.pin} <br/>
{x.add.d.v}
</div>
)
}

Not understanding what's wrong with my Filter function and state in React

I have the following code:
Parent component:
class App extends Component {
state = {
colors: []
};
async componentDidMount() {
const fetched = await fetch("./response.json");
const fetchedJson = await fetched.json();
const res = fetchedJson.colors;
this.setState({
colors: res
});
}
filterItems = (name) => {
const lowerCaseName = name.toLowerCase();
const newColors = [...this.state.colors];
const res = newColors.filter((color) => {
const lowerCaseColorName = color.name.toLowerCase();
return lowerCaseColorName.includes(lowerCaseName);
});
this.setState({
colors: res
});
};
render() {
const { colors } = this.state;
return (
<div>
<InputText filterItems={this.filterItems} />
<AllData colors={colors} />
</div>
);
}
}
And this is my Child component:
class Filter extends Component {
state = {
inputVal: ""
};
onChange = (e) => {
this.setState({
inputVal: e.target.value
});
this.props.filterItems(e.target.value);
};
render() {
return (
<form onSubmit={this.onSubmit}>
<input
type="text"
onChange={this.onChange}
value={this.state.inputVal}
/>
</form>
);
}
}
export default Filter;
There's also another child component called AllData but its job is just displaying out data and put styling on it, so I'm not including it here.
Currently the data displayed are just:
Fire Dragon
Water Horse
Earth Bird
Wood Dog
Wind Cat
Here are my questions:
The filter function works fine when I type in a word into the search box in filter. However, when I backtrack and remove a previous character down to the whole input string, the res array doesn't return its whole original arrays but instead retains the result of the filter only when I type more.
Ex:
When I type in the string "cat", res becomes: [{name: "Wind Cat", id: 5}
However I remove that string by backtracking on the keyboard, res is still at [{name: "Wind Cat", id: 5}. Why is it not going back to returning all of the items, and how do I fix this?
I currently have this code:
onChange = (e) => {
this.setState({
inputVal: e.target.value
});
this.props.filterItems(e.target.value);
};
However, if I change it to:
this.props.filterItems(this.state.inputVal);
and console.log(name) out in the parent component at filterItems, every time I type in a string, it seems like the console.logged name only display the character before.
Ex:
If I type in the string c -> name would be "" (empty)
If I type in the string ca -> name would be c
Why is this happening?
class App extends Component {
state = {
colors: [],
filtered: [],
};
async componentDidMount() {
const fetched = await fetch("./response.json");
const fetchedJson = await fetched.json();
const res = fetchedJson.colors;
this.setState({
colors: res
});
}
filterItems = (name) => {
const lowerCaseName = name.toLowerCase();
const newColors = [...this.state.colors];
const filtered = newColors.filter(color => {
const parts = color.split(' ').map(part => part.toLowerCase());
return parts.reduce((carry, part) => {
return carry ? carry : part.startsWith(lowerCaseName);
}, false);
});
this.setState({
filtered,
});
};
render() {
const { colors, filtered } = this.state;
return (
<div>
<InputText filterItems={this.filterItems} />
<AllData colors={filtered ? filtered : colors} />
</div>
);
}
}
this happens because you filter the array and thus lose the objects. what you can do is have 2 arrays, one with all the data and one with the filtered data. apply the filter to the array with all the data and set the filtered array to the result

How to filter an array and add values to a state

I have the current state as:
const [data, setData] = useState([
{ id: 1, name: "One", isChecked: false },
{ id: 2, name: "Two", isChecked: true },
{ id: 3, name: "Three", isChecked: false }
]);
I map through the state and display the data in a div and call a onClicked function to toggle the isChecked value on click:
const clickData = index => {
const newDatas = [...data];
newDatas[index].isChecked = !newDatas[index].isChecked;
setData(newDatas);
const newSelected = [...selected];
const temp = datas.filter(isChecked==true) // incomplete code, struggling here.
const temp = datas.isChecked ?
};
I have another empty state called clicked:
const[clicked, setClicked] = setState([]). I want to add all the objected whose isChecked is true from the datas array to this array. How can I do this?
I just add checkBox & onChange event instead of using div & onClick event for your understanding
import React, { useState, useEffect } from "react";
import "./style.css";
export default function App() {
const [data, setData] = useState([
{ id: 1, name: "One", isChecked: false },
{ id: 2, name: "Two", isChecked: true },
{ id: 3, name: "Three", isChecked: false }
]);
const [clicked, setClicked] = useState([]);
const clickData = index => {
let tempData = data.map(res => {
if (res.id !== index) {
return res;
}
res.isChecked = !res.isChecked;
return res;
});
setClicked(tempData.filter(res => res.isChecked));
};
useEffect(() => {
setClicked(data.filter(res => res.isChecked));
}, []);
return (
<div>
{data.map((res, i) => (
<div key={i}>
<input
type="checkbox"
checked={res.isChecked}
key={i}
onChange={() => {
clickData(res.id);
}}
/>
<label>{res.name}</label>
</div>
))}
{clicked.map(({ name }, i) => (
<p key={i}>{name}</p>
))}
</div>
);
}
https://stackblitz.com/edit/react-y4fdzm?file=src/App.js
Supposing you're iterating through your data in a similar fashion:
{data.map((obj, index) => <div key={index} onClick={handleClick}>{obj.name}</div>}
You can add a data attribute where you assign the checked value for that element, so something like this:
{data.map((obj, index) => <div key={index} data-checked={obj.isChecked} data-index={index} onClick={handleClick}>{obj.name}</div>}
From this, you can now update your isClicked state when the handleClick function gets called, as such:
const handleClick = (event) => {
event.preventDefault()
const checked = event.target.getAttribute("data-checked")
const index = event.target.getAttribute("data-index")
// everytime one of the elements get clicked, it gets added to isClicked array state if true
If (checked) {
let tempArr = [ ...isClicked ]
tempArr[index] = checked
setClicked(tempArr)
}
}
That will let you add the items to your array one by one whenever they get clicked, but if you want all your truthy values to be added in a single click, then you simply need to write your handleClick as followed:
const handleClick = (event) => {
event.preventDefault()
// filter data objects selecting only the ones with isChecked property on true
setClicked(data.filter(obj => obj.isChecked))
}
My apologies in case the indentation is a bit off as I've been typing from the phone. Hope this helps!

How to update state with usestate in an array of objects?

I'm having some trouble with the React useState hook. I have a todolist with a checkbox button and I want to update the 'done' property to 'true' that has the same id as the id of the 'clicked' checkbox button. If I console.log my 'toggleDone' function it returns the right id. But I have no idea how I can update the right property.
The current state:
const App = () => {
const [state, setState] = useState({
todos:
[
{
id: 1,
title: 'take out trash',
done: false
},
{
id: 2,
title: 'wife to dinner',
done: false
},
{
id: 3,
title: 'make react app',
done: false
},
]
})
const toggleDone = (id) => {
console.log(id);
}
return (
<div className="App">
<Todos todos={state.todos} toggleDone={toggleDone}/>
</div>
);
}
The updated state I want:
const App = () => {
const [state, setState] = useState({
todos:
[
{
id: 1,
title: 'take out trash',
done: false
},
{
id: 2,
title: 'wife to dinner',
done: false
},
{
id: 3,
title: 'make react app',
done: true // if I checked this checkbox.
},
]
})
You can safely use javascript's array map functionality since that will not modify existing state, which react does not like, and it returns a new array. The process is to loop over the state's array and find the correct id. Update the done boolean. Then set state with the updated list.
const toggleDone = (id) => {
console.log(id);
// loop over the todos list and find the provided id.
let updatedList = state.todos.map(item =>
{
if (item.id == id){
return {...item, done: !item.done}; //gets everything that was already in item, and updates "done"
}
return item; // else return unmodified item
});
setState({todos: updatedList}); // set state to new object with updated list
}
Edit: updated the code to toggle item.done instead of setting it to true.
You need to use the spread operator like so:
const toggleDone = (id) => {
let newState = [...state];
newState[index].done = true;
setState(newState])
}
D. Smith's answer is great, but could be refactored to be made more declarative like so..
const toggleDone = (id) => {
console.log(id);
setState(state => {
// loop over the todos list and find the provided id.
return state.todos.map(item => {
//gets everything that was already in item, and updates "done"
//else returns unmodified item
return item.id === id ? {...item, done: !item.done} : item
})
}); // set state to new object with updated list
}
const toggleDone = (id) => {
console.log(id);
// copy old state
const newState = {...state, todos: [...state.todos]};
// change value
const matchingIndex = newState.todos.findIndex((item) => item.id == id);
if (matchingIndex !== -1) {
newState.todos[matchingIndex] = {
...newState.todos[matchingIndex],
done: !newState.todos[matchingIndex].done
}
}
// set new state
setState(newState);
}
Something similar to D. Smith's answer but a little more concise:
const toggleDone = (id) => {
setState(prevState => {
// Loop over your list
return prevState.map((item) => {
// Check for the item with the specified id and update it
return item.id === id ? {...item, done: !item.done} : item
})
})
}
All the great answers but I would do it like this
setState(prevState => {
...prevState,
todos: [...prevState.todos, newObj]
})
This will safely update the state safely. Also the data integrity will be kept. This will also solve the data consistency at the time of update.
if you want to do any condition do like this
setState(prevState => {
if(condition){
return {
...prevState,
todos: [...prevState.todos, newObj]
}
}else{
return prevState
}
})
I would create just the todos array using useState instead of another state, the key is creating a copy of the todos array, updating that, and setting it as the new array.
Here is a working example: https://codesandbox.io/s/competent-bogdan-kn22e?file=/src/App.js
const App = () => {
const [todos, setTodos] = useState([
{
id: 1,
title: "take out trash",
done: false
},
{
id: 2,
title: "wife to dinner",
done: false
},
{
id: 3,
title: "make react app",
done: false
}
]);
const toggleDone = (e, item) => {
const indexToUpdate = todos.findIndex((todo) => todo.id === item.id);
const updatedTodos = [...todos]; // creates a copy of the array
updatedTodos[indexToUpdate].done = !item.done;
setTodos(updatedTodos);
};

React Hooks useState() with Object

What is the correct way of updating state, in a nested object, in React with Hooks?
export Example = () => {
const [exampleState, setExampleState] = useState(
{masterField: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b"
fieldTwoTwo: "c"
}
}
})
How would one use setExampleState to update exampleState to a (appending an field)?
const a = {
masterField: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b",
fieldTwoTwo: "c"
}
},
masterField2: {
fieldOne: "c",
fieldTwo: {
fieldTwoOne: "d",
fieldTwoTwo: "e"
}
},
}
}
b (Changing values)?
const b = {masterField: {
fieldOne: "e",
fieldTwo: {
fieldTwoOne: "f"
fieldTwoTwo: "g"
}
}
})
You can pass new value like this:
setExampleState({...exampleState, masterField2: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b",
fieldTwoTwo: "c"
}
},
})
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'
}));
Generally you should watch out for deeply nested objects in React state. To avoid unexpected behavior, the state should be updated immutably. When you have deep objects, you end up deep cloning them for immutability, which can be quite expensive in React. Why?
Once you deep clone the state, React will recalculate and re-render everything that depends on the variables, even though they haven't changed!
So, before trying to solve your issue, think how you can flatten the state first. As soon as you do that, you will find handy tools that will help dealing with large states, such as useReducer().
In case you thought about it, but are still convinced you need to use a deeply nested state tree, you can still use useState() with libraries like immutable.js and Immutability-helper. They make it simple to update or clone deep objects without having to worry about mutability.
I'm late to the party.. :)
#aseferov answer works very well when the intention is to re-enter the entire object structure. However, if the target/goal is to update a specific field value in an Object, I believe the approach below is better.
situation:
const [infoData, setInfoData] = useState({
major: {
name: "John Doe",
age: "24",
sex: "M",
},
minor:{
id: 4,
collegeRegion: "south",
}
});
Updating a specific record will require making a recall to the previous State prevState
Here:
setInfoData((prevState) => ({
...prevState,
major: {
...prevState.major,
name: "Tan Long",
}
}));
perhaps
setInfoData((prevState) => ({
...prevState,
major: {
...prevState.major,
name: "Tan Long",
},
minor: {
...prevState.minor,
collegeRegion: "northEast"
}));
I hope this helps anyone trying to solve a similar problem.
It's possible to use useReducer hook for managing complex state, instead of useState. To do it, first 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 can update the state by only passing partial updates, like this:
updateState({ occupation: "postman" })
In 2022
If you are looking for the same functionality as this.setState ( came from the class components ) in functional components then this is the answer that helps you a lot.
For Example
You have a state like below and want to update the specific field only from the whole state then you need to use the object destructing every time and sometimes it will be irritating.
const [state, setState] = useState({first: 1, second: 2});
// results will be state = {first: 3} instead of {first: 3, second: 2}
setState({first: 3})
// To resolve that you need to use object destructing every time
// results will be state = {first: 3, second: 2}
setState(prev => ({...prev, first: 3}))
To solve that I came up with the useReducer approach. Please check useReducer.
const stateReducer = (state, action) => ({
...state,
...(typeof action === 'function' ? action(state) : action),
});
const [state, setState] = useReducer(stateReducer, {first: 1, second: 2});
// results will be state = {first: 3, second: 2}
setState({first: 3})
// you can also access the previous state callback if you want
// results will remain same, state = {first: 3, second: 2}
setState(prev => ({...prev, first: 3}))
You can store that stateReducer in utils files and import it in every file if you want.
Here is the custom hook if you want.
import React from 'react';
export const stateReducer = (state, action) => ({
...state,
...(typeof action === 'function' ? action(state) : action),
});
const useReducer = (initial, lazyInitializer = null) => {
const [state, setState] = React.useReducer(stateReducer, initial, init =>
lazyInitializer ? lazyInitializer(init) : init
);
return [state, setState];
};
export default useReducer;
Typescript
import React, { Dispatch } from "react";
type SetStateAction<S> = S | ((prev: S) => S);
type STATE<R> = [R, Dispatch<SetStateAction<Partial<R>>>];
const stateReducer = (state, action) => ({
...state,
...(typeof action === "function" ? action(state) : action),
});
const useReducer = <S>(initial, lazyInitializer = null): STATE<S> => {
const [state, setState] = React.useReducer(stateReducer, initial, (init) =>
lazyInitializer ? lazyInitializer(init) : init,
);
return [state, setState];
};
export default useReducer;
Thanks Philip this helped me - my use case was I had a form with lot of input fields so I maintained initial state as object and I was not able to update the object state.The above post helped me :)
const [projectGroupDetails, setProjectGroupDetails] = useState({
"projectGroupId": "",
"projectGroup": "DDD",
"project-id": "",
"appd-ui": "",
"appd-node": ""
});
const inputGroupChangeHandler = (event) => {
setProjectGroupDetails((prevState) => ({
...prevState,
[event.target.id]: event.target.value
}));
}
<Input
id="projectGroupId"
labelText="Project Group Id"
value={projectGroupDetails.projectGroupId}
onChange={inputGroupChangeHandler}
/>
You have to use Rest parameters and spread syntax (https://javascript.info/rest-parameters-spread) AND set a function with preState as the argument of the setState.
Does not work (missing function)
[state, setState] = useState({})
const key = 'foo';
const value = 'bar';
setState({
...state,
[key]: value
});
Does work!
[state, setState] = useState({})
const key = 'foo';
const value = 'bar';
setState(prevState => ({
...prevState,
[key]: value
}));
I have given both Append, Whole object update, Specific key update examples for the solve
Append and modify both can be done by a simple step. I think this is more stable and safe which has no immutable or mutable dependency.
This is how you can append new object
setExampleState(prevState => ({
...prevState,
masterField2: {
fieldOne: "c",
fieldTwo: {
fieldTwoOne: "d",
fieldTwoTwo: "e"
}
},
}))
Suppose you want to modify again the masterField2 object. There can be two situation. You want to update the whole object or update a specific key of the object.
Update the whole object - So here the whole value for key masterField2 will be updated.
setExampleState(prevState => ({
...prevState,
masterField2: {
fieldOne: "c",
fieldTwo: {
fieldTwoOne: "d",
fieldTwoTwo: "e"
}
},
}))
But what if you wanted to change only fieldTwoOne key inside the masterField2 object. You do the following.
let oldMasterField2 = exampleState.masterField2
oldMasterField2.fieldTwo.fieldTwoOne = 'changed';
setExampleState(prevState => ({
...prevState,
masterField2: oldMasterField2
}))
function App() {
const [todos, setTodos] = useState([
{ id: 1, title: "Selectus aut autem", completed: false },
{ id: 2, title: "Luis ut nam facilis et officia qui", completed: false },
{ id: 3, title: "Fugiat veniam minus", completed: false },
{ id: 4, title: "Aet porro tempora", completed: true },
{ id: 5, title: "Laboriosam mollitia et enim quasi", completed: false }
]);
const changeInput = (e) => {todos.map(items => items.id === parseInt(e.target.value) && (items.completed = e.target.checked));
setTodos([...todos], todos);}
return (
<div className="container">
{todos.map(items => {
return (
<div key={items.id}>
<label>
<input type="checkbox"
onChange={changeInput}
value={items.id}
checked={items.completed} /> {items.title}</label>
</div>
)
})}
</div>
);
}
Initially I used object in useState, but then I moved to useReducer hook for complex cases. I felt a performance improvement when I refactored the code.
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.
useReducer React docs
I already implemented such hook for my own use:
/**
* Same as useObjectState but uses useReducer instead of useState
* (better performance for complex cases)
* #param {*} PropsWithDefaultValues object with all needed props
* and their initial value
* #returns [state, setProp] state - the state object, setProp - dispatch
* changes one (given prop name & prop value) or multiple props (given an
* object { prop: value, ...}) in object state
*/
export function useObjectReducer(PropsWithDefaultValues) {
const [state, dispatch] = useReducer(reducer, PropsWithDefaultValues);
//newFieldsVal={[field_name]: [field_value], ...}
function reducer(state, newFieldsVal) {
return { ...state, ...newFieldsVal };
}
return [
state,
(newFieldsVal, newVal) => {
if (typeof newVal !== "undefined") {
const tmp = {};
tmp[newFieldsVal] = newVal;
dispatch(tmp);
} else {
dispatch(newFieldsVal);
}
},
];
}
more related hooks.
, do it like this example :
first creat state of the objects:
const [isSelected, setSelection] = useState([{ id_1: false }, { id_2: false }, { id_3: false }]);
then change the value on of them:
// if the id_1 is false make it true or return it false.
onValueChange={() => isSelected.id_1 == false ? setSelection([{ ...isSelected, id_1: true }]) : setSelection([{ ...isSelected, id_1: false }])}
I think best solution is Immer. It allows you to update object like you are directly modifying fields (masterField.fieldOne.fieldx = 'abc'). But it will not change actual object of course. It collects all updates on a draft object and gives you a final object at the end which you can use to replace original object.
Your Object for which you want to make state
let teams = {
team: [
{
name: "one",
id: "1"
},
]
}
Making State of teams object
const [state, setState] = useState(teams);
Update State like this
setState((prevState)=>({...prevState,team:[
...prevState.team,
{
name: "two",
id: "2"
}
]}))
After the updation State becomes
{
team: [
{
name: "one",
id: "1"
},
{
name: "two",
id: "2"
}
]
}
To Render items according to current state use Map function
{state.team.map((curr_team) => {
return (
<div>
<p>{curr_team.id}</p>
<p>{curr_team.name}</p>
</div>
)
})}
If you work with boolean values and arrays This can help you:
const [checkedOrders, setCheckedOrders] = useState<Record<string, TEntity>>({});
const handleToggleCheck = (entity: TEntity) => {
const _checkedOrders = { ...checkedOrders };
const isChecked = entity.id in checkedOrders;
if (isChecked) {
delete _checkedOrders[entity.id];
} else {
_checkedOrders[entity.id] = entity;
}
setCheckedOrders(_checkedOrders);
};
Answers are already there but This type isn't mentioned so take look at the example of this kind ...
const[data,setdata]= useState({
username: [
email,
"required",
//...some additional codes
],
password: [
password,
"required|password-5",
//..additional code if any..
],
})
**To update the state variable email in this example in the input field you can add similar code with your variable name **
<Input
onChangeText={(t) => setdata(prevState=>({...prevState,username:{[0]:t}}))}
value={data.username[0]}
/>
I leave you a utility function to inmutably update objects
/**
* Inmutable update object
* #param {Object} oldObject Object to update
* #param {Object} updatedValues Object with new values
* #return {Object} New Object with updated values
*/
export const updateObject = (oldObject, updatedValues) => {
return {
...oldObject,
...updatedValues
};
};
So you can use it like this
const MyComponent = props => {
const [orderForm, setOrderForm] = useState({
specialities: {
elementType: "select",
elementConfig: {
options: [],
label: "Specialities"
},
touched: false
}
});
// I want to update the options list, to fill a select element
// ---------- Update with fetched elements ---------- //
const updateSpecialitiesData = data => {
// Inmutably update elementConfig object. i.e label field is not modified
const updatedOptions = updateObject(
orderForm[formElementKey]["elementConfig"],
{
options: data
}
);
// Inmutably update the relevant element.
const updatedFormElement = updateObject(orderForm[formElementKey], {
touched: true,
elementConfig: updatedOptions
});
// Inmutably update the relevant element in the state.
const orderFormUpdated = updateObject(orderForm, {
[formElementKey]: updatedFormElement
});
setOrderForm(orderFormUpdated);
};
useEffect(() => {
// some code to fetch data
updateSpecialitiesData.current("specialities",fetchedData);
}, [updateSpecialitiesData]);
// More component code
}
If not you have more utilities here : https://es.reactjs.org/docs/update.html
I think a more elegant solution will be to create the updated state object, while retaining the previous values of state. The Object property which is needed to be updated can be provided in the form of array something like this -
import React,{useState, useEffect} from 'react'
export default function Home2(props) {
const [x, setX] = useState({name : '',add : {full : '', pin : '', d : { v : '' }}})
const handleClick = (e, type)=>{
let obj = {}
if(type.length > 1){
var z = {}
var z2 = x[type[0]]
type.forEach((val, idx)=>{
if(idx === type.length - 1){
z[val] = e.target.value
}
else if(idx > 0){
Object.assign(z , z2) /*{...z2 , [val]:{} }*/
z[val] = {}
z = z[val]
z2 = z2[val]
}else{
z = {...z2}
obj = z
}
})
}else obj = e.target.value
setX( { ...x , [type[0]] : obj } )
}
return (
<div>
<input value = {x.name} onChange={e=>handleClick(e,["name"])}/>
<input value = {x.add.full} onChange={e=>handleClick(e,["add","full"])} />
<input value = {x.add.pin} onChange={e=>handleClick(e,["add","pin"])} /><br/>
<input value = {x.add.d.v} onChange={e=>handleClick(e,["add","d","v"])} /><br/>
{x.name} <br/>
{x.add.full} <br/>
{x.add.pin} <br/>
{x.add.d.v}
</div>
)
}

Categories