I am learning ReactJS and needless to say I am an absolute beginner! I am trying to change a specific property in the array of objects which belongs to state. Every object has two properties: name and active. active values are false by default. When I click on the item, I want to change this item's active value to true.
My array is shown inside of the list element and every list element has onClick={() => props.onChangeSelected(lang.name)} method. onChangeSleceted method goes to handleChangeSelected(name) function, however, I couldn't figure out what to write inside of this function.
class Loading extends React.Component {
constructor(props) {
super(props);
this.state = {
text: 'Loading'
};
}
componentDidMount() {
const stopper = this.state.text + '...';
this.interval = window.setInterval(() => {
this.state.text === stopper
? this.setState(() => ({ text: 'Loading' }))
: this.setState((prevState) => ({ text: prevState.text + '.' }))
}, 300)
}
componentWillUnmount() {
window.clearInterval(this.interval);
}
render() {
return (
<p>
{this.state.text}
</p>
)
}
}
function LanguageList (props) {
return (
<div>
<h3>Choose your favorite:</h3>
<ul>
{props.list.map((lang) => (
<li key={lang.name} onClick={() => props.onChangeSelected(lang.name)}>
<span>{lang.name}</span>
</li>
))}
</ul>
</div>
)
}
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
languages: [
{
name: 'all',
active: true
},
{
name: 'javascript',
active: false
},
{
name: 'ruby',
active: false
},
{
name: 'python',
active: false
}
]
}
this.handleChangeSelected = this.handleChangeSelected.bind(this)
}
handleChangeSelected(name) {
this.setState((currentState) => {
const lang = currentState.languages.find((lang) => lang.name === name)
return {}
})
}
render() {
return (
<div>
<LanguageList
list={this.state.languages}
onChangeSelected={this.handleChangeSelected}
/>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
)
</script>
You can do it in a number of ways. All you need to make sure is that you aren't mutating the original state array
handleChangeSelected(name) {
this.setState((currentState) => {
return { languages: currentState.languages.map((lang) => {
if(lang.name === name) {
return {...lang, active: true};
}
return lang;
});
})
}
Try this?
handleChangeSelected(name){
// Find matching element in state
var temp = this.state.languages;
for (var i = 0; i < temp.length; i++){
if (temp[i]["name"] === name){
temp[i]["active"] = true;
}
}
this.setState({
languages: temp
});
}
As listed in the React docs, they recommend creating a new object when calling the setState function. This is of course talking about the updater function syntax (this.setState((prevState, props) => {return {...};});), which I assume the same logic is applied to the syntax used above (passing an object into set state)
The first argument [to setState] is an updater function with the signature:
(prevState, props) => stateChange
(prevState, props) => stateChange prevState is a reference to the
previous state. It should not be directly mutated. Instead, changes
should be represented by building a new object based on the input from
prevState and props.
Related
I'm trying to change the value of an object key from a state array using setState. The change should be in such a way that only a specific value of object should be changed from the array of index i. This index is passed as follows
import React from "react";
import {Button} from 'react-bootstrap';
class StepComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{
step: "Step1",
visited: false
},
{
step: "Step2",
visited: false
},
{
step: "Step3",
visited: false
}
]
};
}
nextStep(i) {
//Change the visited value of object from data[i] array from false to true
//Something like below
this.setState({
data[i]:visited to true
})
}
render(){
let visitSteps = this.state.data;
return(
<div>
{visitSteps.map((visitedStep, index) => (
<div key={index}>
<p>{visitedStep.step}</p>
<Button onClick={() => this.nextStep(i)}>Continue</Button>
</div>
))}
</div>
)
}
}
export default StepComponent
As per the example given aboove on each onClick event the the value of that particular object value of visited is changed from false to true
You can create a variable with the array equals to your data, change the index passed as input and then call a set state passing the new array.
nextStep(i) {
let visitesList = [...this.state.data];
visitesList[i].visited = true;
this.setState({
data: visitesList
})
}
If you just want one step to be true at a time you can use a map function
nextStep(i) {
this.setState({
data: this.state.data.map((e, index) => {
e.visited = i === index;
return e;
})
});
}
Also, when calling the nextStep on the Button, call it by using nextStep(index)
Change specific object property of array.
class App extends React.Component {
state = {
data:[
{
step: "Step1",
visited: false
},
{
step: "Step2",
visited: false
},
{
step: "Step3",
visited: false
}
]
};
handleClick = item => {
const { data } = this.state;
let obj = data.find(a => a.step === item.step);
obj.visited = true;
let filtered = data.filter(a => a.step !== item.step);
this.setState({ data: [obj, ...filtered] });
};
render() {
console.log(this.state.data);
return (
<div>
{this.state.data.map(a => (
<button key={a.step} style={{ color: a.visited ? "red" : "" }} onClick={() => this.handleClick(a)}>
{a.step}
</button>
))}
</div>
);
}
}
Live Demo
I'm trying to change one value inside a nested state.
I have a state called toDoItems that is filled with data with componentDidMount
The issue is that changing the values work and I can check that with a console.log but when I go to setState and then console.log the values again it doesn't seem like anything has changed?
This is all of the code right now
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
toDoItems: null,
currentView: "AllGroup"
};
}
componentDidMount = () => {
fetch("/data.json")
.then(items => items.json())
.then(data => {
this.setState({
toDoItems: [...data],
});
})
};
changeToDoItemValue = (givenID, givenKey, givenValue) => {
console.log(this.state.toDoItems);
let newToDoItems = [...this.state.toDoItems];
let newToDoItem = { ...newToDoItems[givenID - 1] };
newToDoItem.completedAt = givenValue;
newToDoItems[givenID - 1] = newToDoItem;
console.log(newToDoItems);
this.setState({
toDoItems: {newToDoItems},
})
console.log(this.state.toDoItems);
};
render() {
if (this.state.toDoItems) {
// console.log(this.state.toDoItems[5 - 1]);
return (
<div>
{
this.state.currentView === "AllGroup" ?
<AllGroupView changeToDoItemValue={this.changeToDoItemValue}/> :
<SpecificGroupView />
}
</div>
)
}
return (null)
};
}
class AllGroupView extends Component {
render() {
return (
<div>
<h1 onClick={() => this.props.changeToDoItemValue(1 , "123", "NOW")}>Things To Do</h1>
<ul className="custom-bullet arrow">
</ul>
</div>
)
}
}
So with my console.log I can see this happening
console.log(this.state.toDoItems);
and then with console.log(newToDoItems)
and then again with console.log(this.state.toDoitems) after setState
State update in React is asynchronous, so you should not expect updated values in the next statement itself. Instead you can try something like(logging updated state in setState callback):
this.setState({
toDoItems: {newToDoItems},// also i doubt this statement as well, shouldn't it be like: toDoItems: newToDoItems ?
},()=>{
//callback from state update
console.log(this.state.toDoItems);
})
I've read this post: React setState not Updating Immediately
and realized that setState is async and may require a second arg as a function to deal with the new state.
Now I have a checkbox
class CheckBox extends Component {
constructor() {
super();
this.state = {
isChecked: false,
checkedList: []
};
this.handleChecked = this.handleChecked.bind(this);
}
handleChecked () {
this.setState({isChecked: !this.state.isChecked}, this.props.handler(this.props.txt));
}
render () {
return (
<div>
<input type="checkbox" onChange={this.handleChecked} />
{` ${this.props.txt}`}
</div>
)
}
}
And is being used by another app
class AppList extends Component {
constructor() {
super();
this.state = {
checked: [],
apps: []
};
this.handleChecked = this.handleChecked.bind(this);
this.handleDeleteKey = this.handleDeleteKey.bind(this);
}
handleChecked(client_id) {
if (!this.state.checked.includes(client_id)) {
let new_apps = this.state.apps;
if (new_apps.includes(client_id)) {
new_apps = new_apps.filter(m => {
return (m !== client_id);
});
} else {
new_apps.push(client_id);
}
console.log('new apps', new_apps);
this.setState({apps: new_apps});
// this.setState({checked: [...checked_key, client_id]});
console.log(this.state);
}
}
render () {
const apps = this.props.apps.map((app) =>
<CheckBox key={app.client_id} txt={app.client_id} handler={this.handleChecked}/>
);
return (
<div>
<h4>Client Key List:</h4>
{this.props.apps.length > 0 ? <ul>{apps}</ul> : <p>No Key</p>}
</div>
);
}
}
So every time the checkbox status changes, I update the this.state.apps in AppList
when I console.log new_apps, everything works accordingly, but console.log(this.state) shows that the state is not updated immediately, which is expected. What I need to know is how I can ensure the state is updated when I need to do further actions (like register all these selected strings or something)
setState enables you to make a callback function after you set the state so you can get the real state
this.setState({stateYouWant}, () => console.log(this.state.stateYouWant))
in your case:
this.setState({apps: new_apps}, () => console.log(this.state))
The others have the right answer regarding the setState callback, but I would also suggest making CheckBox stateless and pass isChecked from MyApp as a prop. This way you're only keeping one record of whether the item is checked, and don't need to synchronise between the two.
Actually there shouldn't be two states keeping the same thing. Instead, the checkbox should be stateless, the state should only be kept at the AppList and then passed down:
const CheckBox = ({ text, checked, onChange }) =>
(<span><input type="checkbox" checked={checked} onChange={() => onChange(text)} />{text}</span>);
class AppList extends React.Component {
constructor() {
super();
this.state = {
apps: [
{name: "One", checked: false },
{ name: "Two", checked: false }
],
};
}
onChange(app) {
this.setState(
previous => ({
apps: previous.apps.map(({ name, checked }) => ({ name, checked: checked !== (name === app) })),
}),
() => console.log(this.state)
);
}
render() {
return <div>
{this.state.apps.map(({ name, checked }) => (<CheckBox text={name} checked={checked} onChange={this.onChange.bind(this)} />))}
</div>;
}
}
ReactDOM.render(<AppList />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
The problem is any item button click will delete the 1st index item in the array.
I looked at these resources on handling deleting an item in an array in react.
How to remove item in todo list using React
Removing element from array in component state
React Binding Patterns
I've tried changing how my handler is called in TodoList and TodoItemLIst and that causes the handler not to fire on click. I've tried different methods of binding the handler - adding a param has no effect on it -bind(this) breaks it & isn't necessary because I'm using a function.
I've tried setting state different ways using a filter method. No change happens...
this.setState((prevState) => ({
todoItems: prevState.todoItems.filter(i => i !== index)
}));
I'm not understanding where/what the problem is.
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
listItemValue: props.value || '',
todoItems: [
{id: _.uniqueId(), item: 'Learn React.'},
{id: _.uniqueId(), item: 'Improve JS skills.'},
{id: _.uniqueId(), item: 'Play with kittens.'}
]
};
}
handleChange = (event) => {
let value = event.target.value;
this.setState({
value: this.state.value,
listItemValue: value
});
}
handleSubmit = (event) =>{
event.preventDefault();
this.setState({
value: '',
listItemValue: ''
});
}
addTodoItem = () => {
let todoItems = this.state.todoItems.slice(0);
todoItems.push({
id: _.uniqueId(),
item: this.state.listItemValue
});
this.setState(prevState => ({
todoItems: [
...prevState.todoItems,
{
id: _.uniqueId(),
item: this.state.listItemValue
}]
}))
};
deleteTodoItem = (index) => {
let todoItems = this.state.todoItems.slice();
todoItems.splice(index, 1);
this.setState({
todoItems
});
}
render() {
return (
<div className="App">
<h1>Todo List</h1>
<TodoListForm name="todo"
onClick={ ()=>this.addTodoItem() }
onSubmit={ this.handleSubmit }
handleChange={ this.handleChange }
value={ this.state.listItemValue } />
<TodoList onClick={ ()=>this.deleteTodoItem() }
todoItems={ this.state.todoItems }/>
</div>
);
}
}
const TodoList = (props) => {
const todoItem = props.todoItems.map((todo) => {
return (
<TodoListItem onClick={ props.onClick }
key={ todo.id }
id={ todo.id }
item={ todo.item }/>
);
});
return (
<ul className="TodoList">
{todoItem}
</ul>
);
}
const TodoListItem = (todo, props) => {
return (
<li className="TodoListItem">
<div className="TodoListItem__Item">{todo.item}
<span className="TodoListItem__Icon"></span>
<button onClick={ todo.onClick }
type="button"
className="TodoListItem__Btn">×</button>
</div>
</li>
)
};
In the deleteTodoItem method, try just
let todoItems = this.state.todoItems.slice(0, -1);
and remove the call to splice().
With this code, I am able to successfully use setState on a simple object – when I click on "Joey" the name changes to "Igor".
class Card extends React.Component {
myFunc = () => {this.props.change('Igor')};
render() {
return (
<p onClick={this.myFunc}>{this.props.name}</p>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = { name: "Joey" }
}
toggle = (newname) => {
this.setState((prevState, props) => ({
name: newname
}));
}
render() {
return (
<Card change={this.toggle} name={this.state.name} />
);
}
}
But with this code, which has multiple objects nested in an array, setState is either not able to change each name to "Igor" or it must be modified in some way.
class Card extends React.Component {
myFunc = () => {this.props.change('Igor')};
render() {
return (
<p onClick={this.myFunc}>{this.props.name}</p>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
names: [
{
name: "Joey"
},
{
name: "Sally"
},
{
name: "Billy"
},
]
}
}
toggle = (newname) => {
this.setState((prevState, props) => ({
// what can I put here to change the name I click on to "Igor"
}));
}
render() {
const names = this.state.names.map((name, index) => (
<Card key={index} change={this.toggle} {...name} />
))
return (
<div>
{names}
</div>
);
}
}
Even though I know this is not how setState works, I tried to access name by passing index and then writing this.state.names[index].name: newname. No surprises here, it didn't work.
I have researched and cannot find similar questions on SO about this although I have found a lot of mentions with regards to immutability helpers. But I am still not sure if that is the way to go.
What is the best way to use setState to modify objects nested in an array?
Have modified your code and the working example can be found here.
The changes can be found here:
toggle = (index, newname) => {
this.setState((prevState, props) => ({
// Return new array, do not mutate previous state.
names: [
...prevState.names.slice(0, index),
{ name: newname },
...prevState.names.slice(index + 1),
],
}));
}
render() {
const names = this.state.names.map((name, index) => (
// Need to bind the index so callback knows which item needs to be changed.
<Card key={index} change={this.toggle.bind(this, index)} {...name} />
))
return (
<div>
{names}
</div>
);
}
The idea is that you need to pass the index into the callback function via .bind, and return a new state array with the modified name. You need to pass the index so that the component knows which object to change the name to newname.
I would use this for the toggle method:
toggle = (nameYouWantChanged, nameYouWantItChangedTo) => {
this.setState({
names: this.state.names.map(obj =>
obj.name === nameYouWantChanged
? { name: nameYouWantItChangedTo }
: obj
)
})
}