How to update nested(object of object value) state in react - reactjs

I have state like this -
state = {
modal: {
status: {
visible: true
}
}
};
and i tried this
handleShow = e => {
let newState = this.state.modal;
newState.status.visible = false;
this.setState({ modal: newState });
};
First time it makes true->false but i want toggle like(true to false and false to true) when i click.
Here is complete Sample code
class App extends React.Component {
state = {
modal: { status: { visible: true } }
};
handleShow = e => {
let newState = this.state.modal;
newState.status.visible = false;
this.setState({ modal: newState });
};
render() {
return (
<>
<div>
{this.state.modal.status.visible ? (
<div style={showStyle}>"it's showing"</div>
) : (
"It's Hidden"
)}
</div>
<button onClick={this.handleShow}>
{this.state.modal.status.visible ? "Hide" : "Show"}
</button>
</>
);
}
}
const showStyle={
backgroundColor: 'coral',
height: '100px'
}
Can anybody help me with this?.
Thanks.

there is a few things going on here. You are technically setting a reference to the state object when you do let newState = this.state.modal;, so you're not actually creating "new" state. then when you set to false you're just setting the original state object to false and it never toggles back as there is no re render/logic to make it re render and change the state back to false. The most straight forward approach you could try is
handleShow = e => {
this.setState({ modal: { status: { visible: !this.state.modal.status.visible } } });
};
as every time you click it you will be setting the boolean to whatever it was not the last time, essentially doing the toggle I think you're looking for. Look into state and how it is immutable. You should be making a copy of the state and returning a new object technically
// if you were to go more towards the pattern you had going already
handleShow = e => {
let newState = { ...this.state, modal: { status: { visible: this.state.modal.status.visible } } };
this.setState(newState);
}
this spread operator is essentially spreading all the properties from the original state into a new object, then you change the value you want on the newState, then you set the state to that new object which contains all the other parts of the state not modified as well as what you want modified.
there is also something to be said about modifying state the relies on previous state and react setState method being asynchronous. the more solid approach to set state using previous state if you're not using spread would be to pass a callback function to setState instead of an object with the current state as a variable
this.setState(currentState => {
return { modal: { status: { visible: !currentState.modal.status.visible } } }
});

Instead of this newState.status.visible = false; you can try newState.status.visible = !newState.status.visible;

Write newState.status.visible = !newState.status.visible; instead to make it false
handleShow = e => {
let newState = this.state.modal;
newState.status.visible = !newState.status.visible;
this.setState({ modal: newState });
};

It is a bad idea to mutate state.
The best practice is to create a new copy of the object and update it instead and then update the state.
The ... spread operator creates shallow copy of the object. So you would need to spread the properties so that we are keeping it untouched ( not a big deal here as each nesting has only one property, but would break when there is more than 1 property )
handleShow = e => {
const {
modal
} = this.state;
const updatedState = {
...modal,
status: {
...modal.status,
status: {
visible: !modal.status.visible
}
}
}
this.setState(updatedState);
};

Changing State should be coming only from setState function.
by doing const x = obj;
you are making x === obj (both point to the same pointer). therefore changing x will change obj aswell.
you should make a copy of the state you want to change first, and then assign the changed copy to the state.
With ES6 you can copy an object with the spread (...)
Good Luck!

Related

React setState opposite boolean in map function not changing state

I created a simple to-do list in ReactJS. It loads components for to-do items stored in a file "TodoData.js", data is stored as followed in that file:
const todosData = [
{
id:1,
text: "Take out the thrash",
completed: true
},
{
id:2,
text: "Grocery shopping",
completed: false
},
App.js uses a TodoItem.js component to render each to-do item with a map function. TodoItem.js uses conditional rendering:
if (props.item.completed == true) {
return (
<div className="todoclassDone">
<input type="checkbox"
onChange={ () => props.handleChange(props.item.id)}/>
<p className="lalatext"><del>{props.item.text}</del></p>
</div>
)
}
else { .... //same code as above but with other className.
Within App.js I use the TodoItem.js component to render each item in TodoData with a map function; if the data.completed = true background is green, else background is red.
Problem: However, the handleChange(id) function in App.js is not working properly. I loop through all objects in todosData; if the id is similar to the id of checkbox which the user clicked it should change to the opposite boolean value using todo.completed = !todo.completed However, when running this code nothing is happening. The handleChange function:
handleChange(id) {
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id == id) {
todo.completed = !todo.completed;
}
return todo
})
Extra info: Above mentioned problem is especially weird because if I change the boolean value of the checkbox clicked by the user to either false or true it does work. This does not result in the desired behaviour because now I am only able to change the todo.completed once from false to true. ; In this case the handleChange function would look as follows:
handleChange(id) {
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id == id) {
todo.completed = true;
}
return todo
})
Any help is highly appreciated, thanks in advance! :-)
Ciao, you could try to copy state on an array, modify array and set the state wht updated array. Something like:
handleChange(id) {
let result = this.state.todos;
result = result.map(todo => {
if (todo.id === id) todo.completed = !todo.completed;
return todo;
})
this.setState({todos: result})
}
You should return the new state in your setState callback, with the new state object.
Example todo component with relevant code:
class Todo extends Component {
state = {
todos: todosData,
}
handleChange(id) {
this.setState(prevState => {
const todos = prevState.todos.map(todo => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed
}
}
return todo;
});
return { todos };
}
}

Reactjs and Redux update state mutable or immutable?

I'm extremely new with Reactjs and Redux, anyone can explain what is Mutable and Immutable in reactjs/redux?
I'm using Redux to update state, and using it to control my UI layout, but I found one of the method below will not working, although both are also updating the object.
I've a store object such as below:
const initialAppState = {
showSideBar: false,
showSwitcher: false,
showRightPane: false,
menu: [
{
name: "Home",
redirect: "/",
},
{
name: "About",
redirect: "/about",
expanded: false,
childs: [{
name: "Child one",
redirect: "/Child"
}]
},
]
}
Beside that, I have a Pane.js and Menu.js to render my menu list.
Pane.js
const LeftPane = (props) => {
return <List>
{links.map((o, index) => {
return <Menus key={o.name} props={o} index={index} />
})}
</List>
}
Menu.js
const Menus = ({ props, index, onToggleSubMenu }) => {
return <ListItem> ...
}
I'm trying to update the expanded value to true for the menu object, but when I using state.menu.map to change the value, my react component won't re-render? It will execute to the Pane.js, and I can see the expanded = true from the Pane.js props. but it won't execute to the Menu.js?
const AppReducer = (state = initialAppState, action) => {
return {...state,
menu: [
...state.menu.map((m, i) => {
if (i === action.index) {
m.expanded = !state.menu[action.index].expanded;
}
return m;
})
]
}
}
On the other hand, if I update the expanded value from the code below, it works.
const AppReducer = (state = initialAppState, action) => {
state.menu[action.index] = Object.assign({}, state.menu[action.index], {
expanded: !state.menu[action.index].expanded,
});
return {...state, menu: state.menu}
}
What is the different between these two? What is the correct way to update state? Why we should use Object.assign or spread (...) operator to update state? I've read the Redux Immutale Update Patterns, the working code above is more like the Common Mistake mentioned in the link.
There are two things which you should do differently in your AppReducer.
Map function returns a new array and does not mutate the original array, so no need to destruct there.
Inside your map function, you have the reference to the object m, and you are mutating the m by changing m.expanded to !m.expanded. This is where you should actually be returning a new object.
You should write AppReducer as following.
const AppReducer = (state = initialAppState, action) => {
return {
...state,
// No need to destruct when using map, map always returns a new array
menu: state.menu.map((m, i) => {
if (i === action.index) {
// Return a new object, with all properties copied, and expanded's value toggled
return {
...m,
expanded: !m.expanded;
}
}
// Return the original object because no change has been made
return m;
}),
};
};
As for the difference between the spread operator and Object.assign, according to object-rest-spread-proposal, one is syntactic sugar of the other, i.e. {...a} is pretty much an easier way to write Object.assign({}, a);

Pass setState as a callback to another setState

Is it the right way to call setState as a callback in another one? Here a piece of my code.
This is my initial state:
state = {
resumeCollection: [],
filters: {
educationLevels: [],
educationFields: [],
jobAdvertisementId: []
}
};
And this a componentDidMount section:
componentDidMount() {
this.setState(prevState =>( {filters : {
jobAdvertisementId : [...prevState.filters.jobAdvertisementId, {value: this.props.router.query.value, id: this.props.router.query.id}]}
}
)
, () => this.setState(state => {
return({
resumeCollection : state.resumeCollection.filter(resume => resume.jobAdvertisementId == state.filters.jobAdvertisementId[0].id )
});
}
)
)
}
I would advice against doing 2 state updates like that as it will introduce a useless second render.
If you need to set portion of the state based on another portion of the state, you can calculate it and store it outside the return statement inside a variable and used it where you need it.
In your case it might look something like this:
componentDidMount() {
this.setState(prevState => {
const nextJobAdvertisementId = [
...prevState.filters.jobAdvertisementId,
{
value: this.props.router.query.value,
id: this.props.router.query.id
}
];
return {
filters: {
jobAdvertisementId: nextJobAdvertisementId
},
resumeCollection: prevState.resumeCollection.filter(
resume => resume.jobAdvertisementId === nextJobAdvertisementId[0].id
)
};
});
}
Yes you can add another setState as a part of callback to the first setState(), when you want to set another state based on first.
In the below example I set 'b' state based on state 'a':
e.g
const setB = () => {
if(this.state.a)
this.setState({b:"Success"})
else
this.setState({b:"failure"})
}
this.setState({a:true},this.setB)

The cursor backspace doesn’t remove last char of number format in react

I am new to react.
For e.g, input value entered 1,2,3,4 and after the event onChange it takes only numbers, then I can
remove 4,3,2 with backspace but not 1. And in HTML DOM also, the 1 cannot be removed.
class House extends Component {
state = {
room: null,
};
componentDidMount() {
if (this.props.house.rent) {
this.setState({ rent: this.props.house.rent });
}
}
onChange = (field, value, mutate) => {
if (field === "houseroom") {
value = parseInt(value.replace(/[#,]/g, ""));
}
mutate({
variables: {
},
});
this.setState({
[field]: value,
});
};
render(){
const {house} = this.props;
<SomeInput
type="text"
value={
(house.room&&
`$${house.room.toLocaleString("en")}`) ||
""
}
onChange={e => {
e.target.placeholder = "Room";
this.onChange("houseroom", e.target.value, mutate);
}}
}
/>
}
It look like a lot of a problem during the update of the state probably due to overrendering elsewhere in the component try to use prevState instead do ensure that state updating can not conflict.
this.setState(prevState => {
[field]:value;
});
Keep me in touch with the result.
Hope this helps someone !
Need to mention "this.state.room" in the input Value and check the prev state using the componentDidUpdate then "this.setState" here and also finally "this.setState" during the event change. Thanks all

How to update state in react.js?

I'd like to ask if there is any better way to update state in react.js.
I wrote this code below but just updating state takes many steps and I wonder if I'm doing in a right way.
Any suggestion?
How about immutable.js? I know the name of it but I've never used it and don't know much about it.
code
toggleTodoStatus(todoId) {
const todosListForUpdate = [...this.state.todos];
const indexForUpdate = this.state.todos.findIndex((todo) => {
return todo.id === todoId;
});
const todoForUpdate = todosListForUpdate[indexForUpdate];
todoForUpdate.isDone = !todoForUpdate.isDone;
this.setState({
todos: [...todosListForUpdate.slice(0, indexForUpdate), todoForUpdate, ...todosListForUpdate.slice(indexForUpdate + 1)]
})
}
You are using an extra step that you don't need. Directly setting value to the cloned object and restting back to state will work
toggleTodoStatus(todoId) {
const todosListForUpdate = [...this.state.todos];
const indexForUpdate = this.state.todos.findIndex((todo) => {
return todo.id === todoId;
});
todosListForUpdate[indexForUpdate].isDone = !todosListForUpdate[indexForUpdate].isDone;
this.setState({
todos: todosListForUpdate
})
}

Resources