How to add more elements in ReactJS state array? - reactjs

this.state.counter.map is not a function I need to add object and map to counter
and I need to create and push to counter array and map to counter and show in this browser how to do that?
import React, { Component } from 'react';
import Counter from './counter';
class Counters extends Component {
state = {
counter: [
{value:0, id:1},
{value:0, id:2},
{value:0, id:3},
{value:0, id:4},
]
};
// function for create new array and map to him for delete one of counter in browser
DeleteButten = counterId => {
const counter = this.state.counter.filter(c =>c.id !== counterId);
this.setState({counter});
};
// this function for push new object to array and show in browser
AddCounter = () => {
const counter = this.state.counter.push({value:0 ,id:5});
console.log(this.state.counter);
this.setState({counter}); // error this.state.counter.map is not a function i need help to add object and map to counter
};
render() {
return (
<div>
{this.state.counter.map(counter => (
<Counter
key={counter.id}
onDelete{this.DeleteButten}
value={counter.value}
id={counter.id} selected={true}
/>
)}
<button onClick={this.AddCounter} className='btn btn-outline-info btn-sm m-2'> ADD result</button>
</div>
);
}
}
export default Counters;

In your code this line:
const counter = this.state.counter.push({value:0 ,id:5});
You're mutating the state array directly, which is not allowed in React.
You can either (shallow) clone the array and modify it:
// This does a shallow clone of the array.
const newCounter = [...this.state.counter];
// Modify the new array.
newCounter.push({ value: 0, id: 5 });
// Set the new array back to the state using setState.
this.setState({ counter: newCounter });
Or, you can use the shorter syntax for just appending new elements and making a new array at the same time:
// Append a new element to the new cloned array.
this.setState({ counter: [...this.state.counter, { value: 0, id: 5 });
// Or you can put the element at the front if you like :)
this.setState({ counter: [{ value: 0, id: 5 }, ...this.state.counter]);
The triple dot ([...array]) syntax is called spread, and can be used with both arrays and objects to conveniently clone or reconstruct new array or objects! Check out the MDN docs about it here.
One more little thing to improve is the above code works mostly, but note that React setState is async, there's a chance for race condition
Say for example you call your AddCounter method multiple times within the same event loop, the later results may override the previous ones.
Therefore, if you are setting something that depends on the current state, it's recommended to use the callback syntax:
this.setState(state => { counter: [...state.counter, { value: 0, id: 5 }] });
See more details and examples of the async nature of setState in React docs.

If you just want to add element to array here is the code :
this.setState({counter:...this.state.counter,new_count})
new_count is what you are trying to push

Related

React setState hook not updating dependent element if passed a variable as opposed to explicit text

I'm right on the verge of tossing React and just using vanilla JS but thought I'd check here first. I'm simply trying to pass the contents of a variable, which contains an object, into state and have that update the element that depends upon it. If I pass setState a variable containing the object, it doesn't work. If I pass it the explicit text of the object it does.
Using React v 18.0.0
function buttonHandler(e) {
e.preventDefault()
let tmpObject = {...chartData}
tmpObject.datasets[0].data = quoteData.map(entry => entry['3'])
tmpObject.datasets[1].data = quoteData.map(({fastEma}) => fastEma)
tmpObject.datasets[2].data = quoteData.map(({slowEma}) => slowEma)
tmpObject.labels = quoteData.map(entry => new Date(entry.timestamp).toLocaleTimeString())
console.log("from button:", tmpObject)
setChartData(prevState => {
console.log("tmpObject",tmpObject)
return tmpObject
})
return <div>
<button onClick={buttonHandler}>Update</button>
<Line options={chartOptions} data={chartData}/>
</div>
When I run the above, the output of the console.log is exactly as it should be but the element does not update. If I copy the object output from the console and paste it explicitly into the code it does work.
function buttonHandler(e) {
e.preventDefault()
setChartData({...})
I've tried every imaginable variation on the below statement to no avail...
return {...prevState, ...tmpObject}
I'd greatly appreciate any suggestions.
EDIT:
As another test, I added the following HTML element to see if it got updated. It gets updated and shows the expected data. Still, I'm having a hard time understanding why the chart will update if I pass it explicit text but will not if I pass it a variable.
<p>{`${new Date().toLocaleTimeString()} {JSON.stringify(chartData)}`</p>
The issue is that of state mutation. Even though you've shallow copied the chartData state you should keep in mind that this is a copy by reference. Each property is still a reference back into the original chartData object.
function buttonHandler(e) {
e.preventDefault();
let tmpObject = { ...chartData }; // <-- shallow copy ok
tmpObject.datasets[0].data = quoteData.map(entry => entry['3']); // <-- mutation!!
tmpObject.datasets[1].data = quoteData.map(({ fastEma }) => fastEma); // <-- mutation!!
tmpObject.datasets[2].data = quoteData.map(({ slowEma }) => slowEma); // <-- mutation!!
tmpObject.labels = quoteData.map(
entry => new Date(entry.timestamp).toLocaleTimeString()
);
console.log("from button:", tmpObject);
setChartData(prevState => {
console.log("tmpObject",tmpObject);
return tmpObject;
});
}
In React not only does the next state need to be a new object reference, but so does any nested state that is being update.
See Immutable Update Pattern - It's a Redux doc but really explains why using mutable updates is key in React.
function buttonHandler(e) {
e.preventDefault();
setChartData(chartData => {
const newChartData = {
...chartData, // <-- shallow copy previous state
labels: quoteData.map(
entry => new Date(entry.timestamp).toLocaleTimeString()
),
datasets: chartData.datasets.slice(), // <-- new datasets array
};
newChartData.datasets[0] = {
...newChartData.datasets[0], // <-- shallow copy
data: quoteData.map(entry => entry['3']), // <-- then update
};
newChartData.datasets[1] = {
...newChartData.datasets[1], // <-- shallow copy
data: quoteData.map(({ fastEma }) => fastEma), // <-- then update
};
newChartData.datasets[2] = {
newChartData.datasets[2], // <-- shallow copy
data: quoteData.map(({ slowEma }) => slowEma), // <-- then update
};
return newChartData;
});
}
Check your work with an useEffect hook with a dependency on the chartData state:
useEffect(() => {
console.log({ chartData });
}, [chartData]);
If there's still updating issue then check the code of the Line component to see if it's doing any sort of mounting memoization of the passed data prop.

React: after deleting element from array deleted element is shown

Hello I'm having strange issue after deleting an element from array , the same element is shown in the UI but the element is being deleted from array.
I have already seen this : React rendering deleted array element after setState()
this is the gif
this is how I delete the element:
const deleteTaglia = useCallback(
(i) => {
let arraytaglie = variant.taglie;
arraytaglie.splice(i, 1);
setVariant((pv) => {
return { ...pv, taglie: arraytaglie };
});
},
[variant.taglie]
);
this is where I render my array:
{variant.taglie.map((v, i) => (
<Taglia
key={i}
singleTaglia={v}
index={i}
addOrUpdateTaglia={addOrUpdateTaglia}
deleteTaglia={deleteTaglia}
/>
))}
this is my state:
const [variant, setVariant] = useState({
color: "",
codiceArticolo: "",
ean: "",
imagesVariant: [],
taglie: [],
});
Array.splice modifies the array "in place" (see the docs), so when you call setState the change within your array doesn't cause a re-render (because the reference to the array doesn't change). Instead, you need to replace the array itself. There are a few options, but the smallest change from your current code is just to create a new array using the spread operator:
const deleteTaglia = useCallback(
(i) => {
let arraytaglie = variant.taglie;
arraytaglie.splice(i, 1);
setVariant((pv) => {
return { ...pv, taglie: [...arraytaglie] };
});
},
[variant.taglie]
);
In addition, using an index for a key can also create problems when adding/removing items as it isn't clear that the object the you have deleted is no longer in the position it was (as the key is used to detect changes). Change the key property to something unique to each item and your delete will lead to an update as you expect.

react-sortablejs - list won't update and component won't re-render

I'm trying to build a 'put the words in order' typical language learning activity. The user is supposed to start by dragging the first word of a sentence to its right place and move on from there. Meanwhile, I'm checking whether the next word is in the right place. If so, that word/item changes CSS class and becomes not draggable and of a different background colour.
For this CSS class change to happen, I clone the draggable list of words, make the necessary changes and then I setState() with the new updated list. That's when I'm having problems. The list and the component will update occasionally, seems to be at random. I must be doing something wrong when I try to update the list of words in the handleDragEnd() method. Here's part of the code:
export default class Sentence extends Component {
constructor(props) {
super (props)
// prepare this.state.list to be used by SortableJS
let list = this.props.sentenceShuffled.map((word, index) => {
return { id: (++index).toString(), name: word, class: 'sortable' }
})
this.state = {
currentCorrectWord: 0, // stores how many words are correct already while student is sorting
complete: false, // becomes true when sentence is sorted
list: list // this object is used by SortableJS and is calculated in componentWillMount
}
this.handleDragEnd = this.handleDragEnd.bind(this)
}
handleDragEnd() {
let currentCorrectWord = this.state.currentCorrectWord
while ( this.state.list[currentCorrectWord].name === this.props.sentenceInOrder[currentCorrectWord]) {
let newList = _.cloneDeep(this.state.list)
newList[currentCorrectWord].class = 'notSortable'
currentCorrectWord++
/*
this is where (I think) the problem is: setSate won't update states nor re-render component.
*/
this.setState({ currentCorrectWord: currentCorrectWord })
this.setState({ list: newList })
... more code here
}
}
render() {
return (
...more code here
<ReactSortable
list={this.state.list}
setList={newState => this.setState({ list: newState })}
filter=".notSortable"
>
{this.state.list.map(item => (
<MDBBox
className={item.class + ' eachWord'}
key={item.id}
onDragEnd={this.handleDragEnd}
>{item.name}
</MDBBox>
))}
</ReactSortable>
...more code here
)
}
}
What am I doing wrong?

React: Mutate nested states once

So in typical programming scenarios, if you mutate an object, it mutates the object everywhere. However, in React, since states are immutable and only mutable through each's set*, if states are nested in each other, like the scenario shown below, only the currentMember's name will change, and not the version of currentMember in currentTeam, or teams. I'd have to go through each and mutate them one by one, which is cumbersome.
What would be the best way to mutate multiple states at once, or achieve a similar effect? I've done so by indexing, but it's more cumbersome to work with, and i didnt know if there were a textbook hook that fixes this.
import React, { useState } from 'react'
interface Member {
name: string
}
interface Team {
name: string
members: Member[]
}
export default (props: {}) => {
const [teams, setTeams] = useState<Team[]>([
{
name: 'Team One',
members: [{ name: 'Wyatt' }, { name: 'Michael' }]
}
])
const [currentTeam, setCurrentTeam] = useState<Team>(teams[0])
const [currentMember, setCurrentMember] = useState<Member>(currentTeam.members[0])
return (
<>
<h1>${currentMember.name}</h1>
<button onClick={() => setCurrentMember(currentMember => { ...currentMember, name: 'Zach' })}>
Change current member name to Zach!
</button>
</>
)
}
As mentioned, you are making things a bit complicated by using state this way. You should have one base state that contains all of the teams, then reference the bits that are important to you by index.
For example, your teams state is fine as is. Your currentTeam and currentMember states should be indexes or some other reference to the state within teams you want to map to.
So, in specific terms, I'd change the format of your code here like so (forgive me as I don't write TypeScript, so I'm going to straight vanilla javascript to avoid making typos):
import React, { useState } from 'react'
// interface Member {
// name: string
//}
// interface Team {
// name: string
// members: Member[]
//}
export default (props: {}) => {
const [teams, setTeams] = useState([
{
name: 'Team One',
members: [{ name: 'Wyatt' }, { name: 'Michael' }]
}
])
const [currentTeamIndex, setCurrentTeam] = useState(0)
const [currentMemberIndex, setCurrentMember] = useState(0)
return (
<>
<h1>${teams[currentTeamIndex].members[currentMemberIndex]}</h1>
<button onClick={() => setTeams(teams => ({
// Shallow copy the teams via mapping through them
...teams.map((team, teamIndex) => {
// If the current team index isn't the index we're on right now, then just
// return the existing team in its given place.
if (teamIndex !== currentTeamIndex) return team
// If we're at this point, it means the teamIndex matches the currentTeamIndex
// and we need to mutate this. We'll just do that by returning a new object
return {
...team, // Make sure we don't miss anything
...members.map((member, memberIndex) => {
// Same as the outer map, if the current member index isn't the same as the
// given memberIndex, then just return the member we're on, we're not mutating it
if (memberIndex !== currentMemberIndex) return member
return {
...member,
name: 'Zach'
}
})
}
}
})
>
Change current member name to Zach!
</button>
</>
)
}
As you can see, drilling that far down into an object isn't so simple, but it's possible to do without mutating the original data - you can accomplish what you are looking for by reconstructing the data on the fly with Array functions, spread syntax, and index references in state.
You might also want to consider confining this in a reducer, with a specific action containing the team id index, and member id index to make this quite a bit more composable.

array declaration in this.state React

**I'm trying to create an array with 5 values which I could use with nameArray[number].
I think that the declaration of the array is wrong but I don't know how I can fix it.
My idea is that: I have 5 buttons, when I click one of this, only one value of the 5 values in the state array change from false to true.
**
constructor(props) {
super(props);
this.state = {
activeButtons: [false, false, false, false, false]
};
}
cliccato = (e) => {
e.preventDefault();
const number = parseInt(e.target.id);
this.setState(
{
activeButtons: !this.state.activeButtons[number],
},
() => {
console.log(" "+ this.state.activeButtons[number]);
}
);
}
You're updating your state's activeButtons with a single boolean value, rather than an updated array.
You need to generate a new array and modify only the relevant element:
const newArray = [...this.state.activeButtons];
newArray[number] = !newArray[number];
this.setState({
activeButtons: newArray,
});
Declaration of the array is fine. You can make it shorter with Array(5).fill(false).
It's setting state part that needs work. In your current code, you are setting the state to the alternate of a boolean value, instead you need to set it to an array.
this.setState(prevState => ({
activeButtons: prevState.activeButtons.map((val, index) => {
if(index === number) {
return !val;
}
return val;
})
}));
Also, using the functional set state form here
It's because you're overwriting activeButtons every time with only one element value. You need to preserve the other values each time you want to update an element.
Using an Object would be a more graceful data structure in this case though.
this.setState(
{
activeButtons: {
...this.state.activeButtons
[number]: !this.state.activeButtons[number]
}
)

Resources