React useState failing to update two way binding - javascript

I'm trying to set up so that when I type a name in the input of the Person component the state in the App component is updated and in turn updates the value of a prop in Person, however it appears the state change is happening, but the prop isn't updated, can anyone help me figure out what is wrong?
App
const App = () => {
const [persons, setPersons] = useState([
{id: "key1", name: "Daniel", age: "28"},
{id: "key2", name: "John", age: "30"},
{id: "key3", name: "Doe", age: "60"}
]);
const nameChangedHandler = (id, event) => {
console.log(event.target.value);
console.log(id);
const personIndex = persons.findIndex(p => {
return p.id === id;
});
const person = {
...persons[personIndex]
};
person.name = event.target.value;
const pers = [...persons];
persons[personIndex] = person;
setPersons(pers);
console.log(persons);
};
let people = persons.map((person, index) => {
return (
<Person
name={person.name}
key={person.id}
age={person.age}
changed={nameChangedHandler.bind(this, person.id)}
/>
);
});
return <div className="App">{people}</div>;
};
Person
const person = props => (
<div className={style.Person}>
<p onClick={props.click}>
I'm {props.name}, and I am {props.age}!
</p>
<input type="text" onChange={props.changed} value={props.name} />
</div>
);

You are assigning to the wrong variable, try the following:
const pers = [...persons];
pers[personIndex] = person;
And it should work as expected. Since you were updating your state object persons instead of the object you cloned pers, which you used to set the state, your console log was showing the expected output but your state wasn't being updated properly.
Check this working stackblitz

To be honest, I would use a simple map function to change the name of the particular person.
Inside nameChangedHandler function:
const updatedPersons = persons
.map((person) => person.id === id ? {...person, name: event.target.value} : person);
and then update the local state
setPersons(updatedPersons);
It should work as expected.

Related

Update specific object value contained within an array of objects in React

I currently have an array of objects. Each array of objects contains a key of checked with a value of type boolean. I am attempting to loop through the array when a user selects a certain checkbox and updating that objects checked value to either true or false. The issue I am having is spreading the updated object back into the array without creating duplicates. My code is as follows:
import { useState } from "react";
import "./styles.css";
export default function App() {
const [arr, setArr] = useState([
{ id: 1, checked: false, name: "Person 1" },
{ id: 2, checked: true, name: "Person 2" }
]);
const updateCheck = (id) => {
const newArr = [...arr];
const object = newArr.find((r) => r.id === id);
const updatedObject = { ...object, checked: !object.checked };
console.log(updatedObject);
};
return (
<div className="App">
{arr.map((r) => {
return (
<>
<label>{r.name}</label>
<input
type="checkbox"
checked={r.checked}
onClick={() => updateCheck(r.id)}
/>
<br />
</>
);
})}
</div>
);
}
The desired effect I would like to achieve is that if Person 1's checkbox gets clicked I update their checked value to the opposite value. So Person 1 would have a checked value of true after their checkbox was clicked.
attached is a code sandbox https://codesandbox.io/s/elegant-haibt-hycmy?file=/src/App.js
Map the array state to create a new array state. If the item being iterated over has an ID that matches, return the updated object from the callback, otherwise return the existing item.
const updateCheck = (id) => setArr(
arr.map(item => (
item.id !== id ? item : { ...item, checked: !item.checked }
))
);

How Can I use current value of a variable in somewhere else?

I change the name and age dynamiclly by creating two handlers. When I input an age it changes dynamicly. There is no problem here. But the name goes default as well. Because I defined it as a hard-coded value in 'ageChangeHandler'.But i want the name to stay like I did in the 'nameChangeHandler'. Is there something like 'currentValue' or 'lastValue' in Javascript/ES6?
I hope I explained it properly.
Thanks in advance.
I couldnt find any options like currentvalue etc.
nameChangeHandler = (event) =>{
this.setState({
persons: [
{name: "Max" , age: 28},
{name: event.target.value, age: 29},
{name:"Arthur", age:34}
]
})
}
ageChangeHandler = (event) =>{
this.setState({
persons:[
{name: "Max" , age: 28},
{name: "Tom", age: 29},
{name:"Arthur", age:event.target.value}
]
})}
Lines I call handlers:
<Person
name={this.state.persons[0].name}
age={this.state.persons[0].age}/>
<Person
name={this.state.persons[1].name}
age={this.state.persons[1].age}
click={this.switchNameHandler.bind(this, "Way 2")}
Namechanged={this.nameChangeHandler}>My hobbies: coding</Person>
<Person
name={this.state.persons[2].name}
age={this.state.persons[2].age}
Agechanged={this.ageChangeHandler}/>
If you pass a function, instead of an object to setState, you get this signature:
this.setState((prevState, props) => {
// do something with prevState
return { ...prevState }
});
allowing you to use the previous state to create the new one.
so In your case:
this.setState(prevState => {
return {
...prevState,
persons: prevState.persons.map(person => {
if (person.name === "Arthur") return { ...person, age: e.target.value }
return person
})
}
})
or something like that.
You can use an index to dynamically select a user to edit and then just update its attributes:
nameChangeHandler = (event) => {
const { index } = this.state;
this.setState({
persons: this.state.persons.map((item, j) => {
if (j === index) {
return {
...item,
name: event.target.value,
};
} else {
return item;
}
}),
});
}
You could also create a universal function to update any number of parameters:
<input
type="text"
// Here we send a value, n index (can be from state) and the parameter to update
onChange={(e) => { this.changeHandler(e.target.value, 0, 'name') }}
/>
changeHandler = (value, index, attr) => {
// index is the object in the array that we want to update
// attr is the key
this.setState({
persons: this.state.persons.map((item, j) => {
if (j === index) {
return {
...item,
[attr]: value,
};
} else {
return item;
}
}),
});
}
Declared an ID for all of persons :
state = {
persons: [
{id:"25343", name:"Max", age: 28},
{id:"24323ad ", name: "Tom", age: 29},
{id:"asdavd231", name:"Arthur", age:34}
],
Updated them with their ID's :
ageChangeHandler = (event,id) =>{
const personIndex = this.state.persons.findIndex(p=>{
return p.id === id;
})
const person = {
...this.state.persons[personIndex]
}
person.age = event.target.value;
const persons = [...this.state.persons]
persons[personIndex] = person;
this.setState({persons:persons})}
Rendered them in a map:
<div>
{this.state.persons.map((person,index)=>{
return <Person
click={() => this.deletePersonHandler(index)} //map'ten gelen index deletePersonHandler'a pareametre olarak gönderiliyor
name={person.name} //map'in 1.parametresi. (persons'u işaret ediyor)
age={person.age}
key={person.id}
nameChanged={(event)=>this.nameChangeHandler(event,person.id)}
ageChanged={(event)=>this.ageChangeHandler(event,person.id)}/>
})}
</div>

React. Delete specific row from the table

I have a component that is a table.
Each row of this table is also component.
class FormulaBuilder extends Component {
constructor(props) {
super(props);
this.state = {
rows: [{}]
}
}
handleAddRow = () => {
const item = {};
this.setState({
rows: [...this.state.rows, item]
});
};
handleRemoveSpecificRow = (idx) => {
const rows = [...this.state.rows]
rows.splice(idx, 1)
this.setState({ rows })
}
render() {
return (
{
this.state.rows.map((item, idx) => {
return (
<React.Fragment key={idx}>
<ConcoctionRow
removeSpecificRow={(idx) =>this.handleRemoveSpecificRow(idx)}
id={idx} />
</React.Fragment>);
})
});
}
}
In the child component there is a button. When clicked, the event from the parent component is called:
class ConcoctionRow extends Component {
constructor(props) {
super(props);
}
handleRemoveSpecificRow = () => {
this.props.removeSpecificRow(this.props.id);
}
}
The properties passed the index of the array. But only the last line is always deleted not specific.
Where is my bad? P.S. I am new in JS.
A couple of things, you want to avoid using .splice() to update your arrays in components. Often times this actually ends up mutating your original state instead of creating a new one. A direct violation of React concepts.
Likewise lets try some stuff out on the console:
const arr = [1, 2, 3] <-- this is your state
const newArr = arr <-- you created a reference of your state. This does not actually create a new copy.
Now if you splice the newArr
newArr.splice(0, 1) <-- now newArr = [2, 3]
Well guess what, you also mutated your original state.
arr <-- is now also [2, 3]
A common misconception in JavaScript is that when you create a new variable that equals an existing variable, you expect that it actually creates a new copy.
let cat = {id: 1, name: "bunny"}
let myCat = cat
This is not actually the case, instead of explicitly creating a new copy, your new variable points to the same reference of the original object it is derived from. If I did something like:
myCat.age = 2 <-- Now myCat has a new property of age.
myCat <-- {id: 2, name: "bunny", age: 2}
BUT, because these two variables point to the same reference. You also mutate the original cat object as well
cat <-- {id: 2, name: "bunny", age: 2}
Use array.filter() instead to create a completely new array.
Here's an example with your code as well as a sandbox for reference: https://codesandbox.io/s/heuristic-nobel-6ece5
import React from "react";
import ConcoctionRow from "./ConcoctionRow";
class FormulaBuilder extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: [{}, {}, {}]
};
}
handleAddRow = () => {
const item = {};
this.setState({
rows: [...this.state.rows, item]
});
};
handleRemoveSpecificRow = idx => {
const { rows } = this.state;
const updatedRows = rows.filter((row, index) => {
return index !== idx;
});
this.setState({
rows: updatedRows
});
};
render() {
return (
<div>
{this.state.rows.map((item, idx) => {
return (
<React.Fragment key={idx}>
<ConcoctionRow
removeSpecificRow={this.handleRemoveSpecificRow}
id={idx}
/>
</React.Fragment>
);
})}
</div>
);
}
}
export default FormulaBuilder;
I show the pattern I would use for this case. I recommend to use id instead of array index for items.
filter array function is immutable (it creates a new array, not mutates the previous one), so ok to use in set state. The functional form of setState is also a good stuff.
const Row = ({ onClick, children, id }) => (
<li>{children} <button onClick={() => onClick(id)}>Delete</button></li>
)
class App extends React.Component {
state = {
list: [
{id: 1, label: 'foo' },
{id: 2, label: 'bar' }
]
}
handleDelete = id => {
this.setState(prevState => ({
list: prevState.list.filter(row => (
row.id !== id
))
}))
}
render(){
const { list } = this.state;
return (
<ul>
{list.map(({ id, label }) => (
<Row id={id} onClick={this.handleDelete}>{label}</Row>
))}
</ul>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Copy object from one array of objects to another in React

I have this.props.person like this:
person = [{id: 1, name: John, age: 20},
{id: 2, name: Kate, age: 30},
{id: 3, name: Mike, age: 25}]
And I have empty this.state.newPerson: [].
Function get id value, searching object with this id in this.props.person, and add to this.state.newPerson this object. It can be repeats a few times.
For example: I call funcion addPerson twice with id=1 and id=3. In result I should get
this.state.newPerson: [{id: 1, name: John, age: 20}, {id: 3, name: Mike, age: 25}].
I tried:
addPerson(idPerson) {
const list = this.state.newPerson;
const personToList = this.props.person.find(el => el.id === idPerson);
const newP = Object.assign(list, { personToList });
this.setState({ newPerson: newP });
}
In fact, I got something like [personToList {id: ...}]
How can I fix it?
why do you use Object.assign if this.state.newPerson is an array?
Just use
list.push(personToList) but set your state like this this.state.newPerson = [];
You want to add personToList to the list array instead of assigning the entire array with the object { personToList: personToList }.
You could use the spread syntax instead.
Example
class App extends React.Component {
state = { newPerson: [] };
addPerson = personId => {
const list = this.state.newPerson;
if (list.some(el => el.id === personId)) {
return;
}
const personToList = this.props.person.find(el => el.id === personId);
const newP = [...list, personToList];
this.setState({ newPerson: newP });
};
render() {
return (
<div style={{ display: "flex" }}>
<div>
{this.props.person.map(p => (
<div id={p.id} onClick={() => this.addPerson(p.id)}>
{p.name}
</div>
))}
</div>
<div>
{this.state.newPerson.map(p => (
<div id={p.id} onClick={() => this.addPerson(p.id)}>
{p.name}
</div>
))}
</div>
</div>
);
}
}
ReactDOM.render(
<App
person={[
{ id: 1, name: "John", age: 20 },
{ id: 2, name: "Kate", age: 30 },
{ id: 3, name: "Mike", age: 25 }
]}
/>,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Object.assign is not right for this use case scenario. You're trying to add to an array on the state.
Try this instead:
addPerson(idPerson) {
let list = [...this.state.newPerson];
let personToList = this.props.person.find(el => el.id === idPerson);
list.push(personToList);
this.setState({
newPerson: list
});
}
Object.assign is used to combine two javascript object.personToList is already an object.
const newP = Object.assign(list, personToList);
Actually,you could use push to fix it.
const newP = list.push(newP)
find returns only the first matching item. Use filter to get all of them.
Check the documentation:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
try this:
addPerson(idPerson) {
this.setState({
newPerson : this.state.newPerson.concat(this.props.person.find( i => i.id === idPerson))
})
}
}
or
addPerson(idPerson) {
this.setState({
newPerson : [this.state.newPerson, this.props.person.find( i => i.id === idPerson)]
})
}

Update array of object in React Js

I want to update question name on onChange event. But problem is every question changes if I change only one ques.How to fix it ?
class QuestionCreate extends React.Component {
constructor(props){
super(props);
this.state = {
datas: [],
default_question: {
isNew: true,
question: {
name: 'Click to write the question text',
answer_options: [
{name: 'click to write choice 1', 'choice': true},
{name: 'click to write choice 1', 'choice': true},
{name: 'click to write choice 1', 'choice': true},
]
}
}
}
}
onChangeQuestion(qustions, index, event){
let all_data = this.state.datas;
let currentQuestion = all_data[index].question
all_data[index]['question']['name'] = event.target.value;
console.log(all_data[index]['question']['name'])
this.setState({datas: all_data})
}
displayRow(){
let rowData = this.state.datas.map( (data, index) =>{
console.log("this is data:", data, index);
return(
<div className="well" key={ index }>
<h3>Q. <input type="text" onChange={ this.onChangeQuestion.bind(this, data, index) } value={ data.question.name } /></h3>
<ul>
<li>option</li>
</ul>
</div>
)
})
return rowData;
}
You are mutating your state directly.
let all_data = this.state.datas;
let currentQuestion = all_data[index].question
all_data[index]['question']['name'] = event.target.value;
Use:
let all_data = JSON.parse(JSON.stringify(this.state.datas));
let currentQuestion = all_data[index].question;
all_data[index]['question']['name'] = event.target.value;
this.setState({datas: all_data});
These questions may also be helpful.
Deep merge of complex state in React
What is the most efficient way to deep clone an object in JavaScript?
it is working after editing onChangeQuestion function.
onChangeQuestion(qustions, index, event){
let all_data = JSON.parse(JSON.stringify(this.state.datas));
let currentQuestion = all_data[index].question
all_data[index]['question']['name'] = event.target.value;
this.setState({datas: all_data});
}

Categories

Resources