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)]
})
}
Related
I have this array of objects in my calendar project:
const [selectedDay, setSelectedDay] = useState({});
const [weekday,setWeekDays] = useState([
{name: 'Su',sortedDays:[{dayNum:1,chosen:false}]},
{name: 'Mo',sortedDays:[{dayNum:21,chosen:false}},
{name: 'Tu',sortedDays:[{dayNum:15,chosen:false}},
{name: 'We',sortedDays:[{dayNum:11,chosen:false}},
{name: 'Th',sortedDays:[{dayNum:23,chosen:false}},
{name: 'Fr',sortedDays:[{dayNum:31,chosen:false}},
{name: 'Sa',sortedDays:[{dayNum:30,chosen:false}},
])
What I'm doing
I am mapping this array as buttons and give users ability to select the day on calendar
What I'm trying to achieve
Whenever user clicks on the button, the selected button must change background color.
What I tried
First I tried to make this work like this:
const selectWeekDay = (child, info) => {
const updateSortedDays = info.sortedDays.map((day) =>
day.dayNum === child.dayNum
? {...day, chosen: !day.chosen}
: {...day, chosen: false},
);
const updatedWeekDay = weekday.map((el) => {
return el.name === info.name ? {...el, sortedDays: updateSortedDays} : el;
});
setWeekDays(updatedWeekDay);
};
And it worked but the issue was that it was not working on every array inside my array of objects.
So I tried to change it like this:
const updateWeekDays = () => {
let x = [...weekday];
for (let i = 0; i < x.length; i++) {
x[i].sortedDays.map((day) =>
selectedDay.dayNum === day.dayNum
? {...day, chosen: true}
: {...day, chosen: false},
);
}
return x;
};
I am calling this function in useEffect only after this validation:
useEffect(() => {
if (selectedDay.dayNum) {
setWeekDays(updateWeekDays());
}
}, [selectedDay]);
But sadly this workaround is not working and I don't understand why.
Any suggestions please?
DEMO:
https://replit.com/#NikitaZotsik/MultipleArrayMap#index.js
Try to modify your updateWeekDays function like below:-
const updateWeekDays = () => {
const copyWeekDay = [...weekday];
return copyWeekDay.map(week => {
week.sortedDays = week.sortedDays.map(day => {
if (day.dayNum === selectedDay.dayNum) {
day.chosen = !day.chosen;
}
return day;
});
return week;
});
};
const {
useState,
useEffect
} = React
const App = () => {
const chooseDay = (days, dayName) => {
const result = [...days]
result.forEach(day => day.sortedDays[0].chosen = false);
const dayToChange = result.find(({ name }) => name === dayName)
dayToChange.sortedDays[0].chosen = true;
return result;
}
const [weekDays,setWeekDays] = useState([
{name: 'Su',sortedDays:[{dayNum:1,chosen:false}]},
{name: 'Mo',sortedDays:[{dayNum:21,chosen:false}]},
{name: 'Tu',sortedDays:[{dayNum:15,chosen:false}]},
{name: 'We',sortedDays:[{dayNum:11,chosen:false}]},
{name: 'Th',sortedDays:[{dayNum:23,chosen:false}]},
{name: 'Fr',sortedDays:[{dayNum:31,chosen:false}]},
{name: 'Sa',sortedDays:[{dayNum:30,chosen:false}]},
])
return <div>
{weekDays.map(
({ name, sortedDays: [{dayNum, chosen}] }) =>
<button
key={name}
style={{ backgroundColor: chosen && 'green' }}
onClick={() => setWeekDays(chooseDay(weekDays, name))}>
{ name }
</button>
)}
</div>
}
ReactDOM.render( < App / > , app);
<script crossorigin src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id=app>
Have an array of objects and want to update only the stock property of the object with an increaseQuantity() function. how can I go about that?
increaseQuantity = (id) => {
let newData = this.state.data.map(obj => {
if (obj.id === id) {
//Block of Code
}
return obj
})
this.setState({ newData })
console.log(newData)
}
sample data are as follows:
const Items = [
{ id: '0', text: 'Alvaro Beer', stock: 500 },
{ id: '1', text: 'Malta Guinnesse', stock: 200 }
Render Element :
<FlatList
data={Items}
renderItem={({ item }) => (
<Products
id={item.id}
text={item.text}
price={item.price}
stock={item.stock}
onSwipeIncrease={() => this.increaseQuantity()}
onSwipeDecrease={() => console.log('Sub')}
onSwipeAddToCart={() => console.log('Cart')}
/>
)}
keyExtractor={item => item.id}
/>
Check this simple solution
increaseQuantity = id => {
let updateItems = [...this.state.items];
// get the index according to id
let index = this.state.items.findIndex(obj => obj.id == id);
// check whether passed id exits in the items
if (!!index) {
// suppose you want to change stock value to 2000
updateItems[index].stock = 2000;
this.setState({
items: updateItems
});
}
};
In order to do that your state should like this
state = {
items: [
{ id: "0", text: "Alvaro Beer", stock: 500 },
{ id: "1", text: "Malta Guinnesse", stock: 200 }
]
}
Hope this helps you. Feel free for doubts.
I have the following component
import {h, Component} from 'preact'
import {getPersons} from '../../lib/datalayer'
import Person from '../person'
import {SearchInput} from '../search'
export default class Persons extends Component {
state = {
allPersons: [],
persons: [],
search: ''
}
async fetchData () {
try {
const allPersons = await getPersons()
this.setState({allPersons: allPersons.slice(), persons: allPersons.slice()})
} catch (error) {
....
}
}
constructor (props) {
super(props)
this.state = {
allPersons: [],
persons: [],
search: ''
}
this.fetchData()
}
onSearchInput = (search) => {
if (search === '') {
this.setState({search: search, persons: this.state.allPersons.slice()})
} else {
const persons = this.state.allPersons.filter(p => p.name.toLowerCase().includes(search.toLowerCase())).slice()
this.setState({search: search, persons: persons)})
}
}
render () {
const {persons} = this.state
return (
<div>
<SearchInput onInputChange={this.onSearchInput} placeHolder={'filter: name'} />
{persons.map(p => <Person person={p} />)}
</div>
)
}
}
The page renders a list of Persons and it has a filter on top. The filter seems to work fine, I tested it by doing a console.log of the results are just fine
The problem is that, if my list contains the objects:
[{name: 'thomas'}, {name: 'john'}, {name: 'marcus'}, {name: 'usa'}]
And I write in the search input: 'us'
The filter works fine and the result is:
[{name: 'marcus'}, {name: 'usa'}] \\ (the expected result)
In the page this objects are rendered
[{name: 'thomas'}, {name: 'john'}] \\ (wrong, this are the two first elements of the list)
If I search: 'joh'
The filter's result is
[{name: 'john'}] \\ (this is fine)
And the page renders only
[{name: 'thomas'}] \\ (the first element in the list)
It looks like the amount of elements that are rendered it's fine, but the content of the childs of the list is not beeing re-rendered.
Whats's wrong with my code?
React uses keys on the children of a list to determine which items changed and which of them remains the same. Since you have not specified a key on person, it takes index to be the key.
When index is key, you can see how shortening the list to two items, shows up the first two items in the list (the other indices are now missing). To get around this, you have to give a unique identifier on the person as key.
From your object, assuming name is unique (it usually isn't):
{persons.map(p => <Person person={p} key={p.name} />)}
Why are keys necessary - Docs
I cannot reproduce the error with react, did remove some unneeded slice and added unique id to each element (React will complain if you do not give each element a unique key and maybe so will preact).
const Person = React.memo(props => (
<pre>{JSON.stringify(props, undefined, 2)}</pre>
));
class Persons extends React.Component {
state = {
allPersons: [
{ name: 'aaa', id: 1 },
{ name: 'aab', id: 2 },
{ name: 'abb', id: 3 },
{ name: 'bbb', id: 4 },
{ name: 'bbc', id: 5 },
],
persons: [
{ name: 'aaa', id: 1 },
{ name: 'aab', id: 2 },
{ name: 'abb', id: 3 },
{ name: 'bbb', id: 4 },
{ name: 'bbc', id: 5 },
],
search: '',
};
onSearchInput = search => {
if (search === '') {
//slice not needed here
this.setState({
search: search,
persons: this.state.allPersons,
});
} else {
//filter already copies allPersons
const persons = this.state.allPersons.filter(p =>
p.name.toLowerCase().includes(search.toLowerCase())
);
this.setState({ search: search, persons: persons });
}
};
render() {
const { persons } = this.state;
return (
<div>
<input
type="text"
value={this.state.search}
onChange={e => this.onSearchInput(e.target.value)}
placeHolder={'filter: name'}
/>
{persons.map(p => (
<Person person={p} key={p.id} />
))}
</div>
);
}
}
ReactDOM.render(
<Persons />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
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>
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.