Is it possible to update object using setState? I tried this code:
import moment from 'moment';
constructor() {
super();
this.state = {
pressedDate:{
},
}
}
updateState = (day) => {
let updateDay = moment(day.dateString).format(_format);
//When I console.log updateDay, I get this '2019-06-30'
console.log(updateDay,'updateDay')
//I want to update pressedDate with updateDay which is '2019-06-30'
this.setState({
pressedDate:{
updateDay : {dots: [vacation, massage, workout], selected: true}
}
})
}
I am trying to update my object with updateDay but nothing happens when I run this code.
You've said "nothing happens," but that code clearly replaces this.state.pressedDate with a new object with just the updateDay property.
If you meant to add/update updateDay without completely replacing pressedDate (that is, if it has other properties), you'd do that by making a copy and then updating the copy. The idiomatic way to do that uses property spread:
this.setState(({pressedDate}) => ({
pressedDate: {
...pressedDate,
updateDay : {dots: [vacation, massage, workout], selected: true}
}
}));
Two key things to note there:
It's using the callback version of setState. Because state updates are asynchronous, you must use the callback version whenever setting state (an updated pressedDate) based on existing state (the rest of pressedDate other than updateDay).
...pressedDate spreads out the properties of the current version into the new object being created. Then we add updateDay, which will overwrite any previous one.
Related
I have a class component. I've added this line in the constructor:
this.handleChangedCells = this.handleChangedCells.bind(this);
I register a SocketIO listener in componentDidMount and call a function like so
socket.on("changedCells", this.handleChangedCells);
I am receiving the data as I console.log it once I receive it. The problem is that the function calls this.setState and it does not update the component. I have
handleChangedCells(data) {
console.log(data);
let new_board = this.state.board;
data.map(arr => {
arr.map(obj => {
new_board[obj.ind[0]][obj.ind[1]] = {
color: obj.color,
alive: obj.alive
};
});
});
this.setState({ board: new_board });
}
The component that I have is as follows:
render() {
return (
<div className="boardContainer" style={container_style}>
{this.state.board.map((row, r_ind) => {
return (
<div className="row" key={r_ind}>
{row.map((col, c_ind) => {
return (
<Cell
key={[r_ind, c_ind]}
cell_row_index={c_ind}
cell_col_index={r_ind}
socketProps={socket}
colorProps={this.state.color}
clickedColorProps={col.color}
aliveProps={col.alive}
/>
);
})}
</div>
);
})}
</div>
);
}
As you can see, the component depends on the state which I update however, the component is not updated... Any idea of what is happening here?
EDIT: Here's a link to my project https://github.com/Dobermensch/ExpressTest.git The file in question is /client/components/Game.js
Edit 2: board is an array of arrays containing objects {color: [x,y,z], alive: boolean} and I am getting the changed cells, (dead cells) and marking them as dead in the board so ideally I would go the the dead cell's index and mark it as dead by board[row][col] = {color: [x,y,z], alive: false}
Edit 3: data structure of board is [[{}.{},{}],[{},{},{}]] and I am changing the properties of objects within the array
I had faced similar issue while I had the combination of nested objects and array. I think setState doesn't handle nested updates very well.
Another problem is in your code. You are actually mutating the state.
let new_board = this.state.board; Here the board is an array. Javascript does not deep clone array and object. You are assigning the refererence of the board array to the new variable.
So, what you can do is just deep clone the board array.
let new_board = this.state.board.map(item => {...item}); // You may need to handle the multiple levels.
Also JSON.stringy and JSON.parse should work.
let new_board = JSON.parse(JSON.stringify(array));
Once, you have the deep clone of the state. You can modify the data and setState should trigger the rerender.
Update 1:
As you are using the state of the Cell to update it. The prop values are not updated on re-render.
So you should use getDerivedStateFromProps in your Cell component to make the prop data available to state.
static getDerivedStateFromProps(props, state) {
return {
r: props.clickedColorProps[0],
g: props.clickedColorProps[1],
b: props.clickedColorProps[2],
alive: props.aliveProps
};
}
Note that the getDerivedStateFromProps will execute on every rerender of the component, even when the state change. So, you should have a condition inside the method to return the proper data.
Please don't do a deep copy with JSON.parse, it is a really bad idea because you're creating new objects for everything and not only the things that change. If you know what pure components are then you know why JSON.parse is bad.
You can change only the things that need to change with the following code:
handleChangedCells(data) {
console.log(data);
let new_board = [...this.state.board];
data.forEach(arr => {
arr.forEach(obj => {
//shallow copy only if it's not copied already
if (
new_board[obj.ind[0]] ===
this.state.board[obj.ind[0]]
) {
new_board[obj.ind[0]] = [
...new_board[obj.ind[0]],
];
}
new_board[obj.ind[0]][obj.ind[1]] = {
color: obj.color,
alive: obj.alive,
};
});
});
this.setState({ board: new_board });
}
If it still "doesn't work" then I suggest creating a sandbox demonstrating the problem. Instead of a real xhr you can just return changed sample data.
Demo of it working is here
could you please tell me how to get updated value from state.here is my code
https://codesandbox.io/s/cool-ives-0t3yk
my initial state
const initialState = {
userDetail: {}
};
I enter 10 digit number on input field and press enter and update the user detail like this
const onSubmit = async values => {
if (values.mobile.length === 10) {
setUserDetail({ msdin: values.mobile });
console.log(userDetail);
}
};
setUserDetail({ msdin: values.mobile }); here I am updating my userdetail .
and try to console the update value like this
console.log(userDetail); .it is showing currently undefined.but expected output is {msdin:'9999999999'} (or whatever it is type in input field)
The problem is that you are using hooks and it's not synchronised, it's async. Therefore, accessing the detail immediately after setting the value will not be possible. If you want to access the data there, you will have to use values.mobile
The state will keep the last value until the next render is called.
You can see this information on react-hooks document
During subsequent re-renders, the first value returned by useState will always be the most recent state after applying updates.
So, the code should look like:
const onSubmit = async values => {
if (values.mobile.length === 10) {
const newUserDetailState = { msdin: values.mobile }
setUserDetail(newUserDetailState);
// do your stuffs with the newUserDetailState instead of userDetail
console.log(newUserDetailState);
}
};
The state setter setUserDetail is async, that means that the new state value won't be available immediately.
To see if the state update use useEffect like this :
useEffect(() => {
console.log('useEffect -> UserDetail : ', userDetail);
}, [userDetail]);
React Newbie here.
I'm trying to match the value of a specific id located in my state, so I can change some value before updating the database via my api.
The state is
state = {
library: []
}
and then with when the axios changes the state the array looks something like:
0:{_id:"", time:"", value:""},
2:{_id:"", time:"", value:""}
When I run console.log, it reads it like this.
(2) [{…}, {…}]0: {_id: "5c82803ad634ea0bebfb3eff", time: "2019-03-08T14:46:18.663Z", value:""}1: {_id: "5c827fb9d634ea0bebfb3efe", time: "2019-03-08T14:44:09.818Z", value:""}
So basically when I type in a specific input field, identified by it's _id, I need to update the value state of that specific state.
Here's the code I have written so far. _id is the unique key of the input field and event value what I'm typing.
updateRead = (_id, event) => {
console.log(_id);
console.log(event.target.value)
this.setState(?????)
};
Help would be much appreciated!
Cheers
You can use the array map method on the library array in your state, and just return the elements as is if the _id doesn't match, and update the value if the _id does match.
updateRead = (_id, event) => {
const { value } = event.target;
this.setState(prevState => ({
library: prevState.library.map(read => {
if (read._id !== _id) {
return read;
}
return {
...read,
value
};
})
}));
};
Two of the fundamental rules of state in React are:
Never modify state directly
Use the callback version of setState when you're setting state based on existing state (more)
Separately, you can't access properties on the synthetic event after the event handler has returned, so you'll want to grab the value before calling setState (since the call to your callback will be asynchronous).
Within the callback, you copy the array, find the relevant object, copy it, and set the value.
So:
updateRead = (_id, event) => {
const {value} = event.target;
this.setState(({library}) => ({
library: library.map(entry => entry._id === _id ? {...entry, value} : entry)
}));
};
map creates a new array from the previous array's entries. The callback returns a new object if the IDs match (using property spread notation) with an updated value, or the original object (since we don't have to copy objects we aren't modifying).
I have a model Component in my ReactJs project, where I have a picture being, show and I want to pass the data of the picture, that a user clicked on, it can neither be raw data or a URL.
I have made a handle, that can both delete the picture (if pressed with the Ctrl key), or just open up the modal if clicked normally
showModalSpeceficHandler = (event, image) =>{
let index = this.state.images.indexOf(image)
if(event.ctrlKey){
let temp = this.state.images.slice(); //makes a shallow copy of the array
temp.splice(index, 1); //remove item
this.setState(prevState => ({ images: temp }));
}else{
console.log(image); // logs the data of the clicked image
this.setState(
state => ({imageModalData: image}),
() => this.showModalHandler()
);
console.log(this.state.imageModalData) //logs the data of the last added image
}
}
so the issue now is as mentioned in the comments, that the state is not set correctly. I was suspecting that the showModalHandler would change the state but
it simply sets the state, if it should be shown or not:
showModalHandler = () =>{
this.setState({showModal: !this.state.showModal})
}
What is happening, or overwriting the state, since it is not being set correctly
setState is an asynchronous operation.
When your setState call needs to refer to the old state you should use the alternative setState signature where you pass a function as first argument:
setState((state) => ({ showModal: !state.showModal }));
See https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
This is not technically a callback. The callback argument is the second setState parameter which is rarely used (so, more or less you should never use it).
See https://reactjs.org/docs/react-component.html#setstate
try to bind your showModalHandler function to this in your constructor like this :
constructor(props){
super(props)
/* your state*/
this.showModalHandler = this.showModalHandler.bind(this)
}
handleOptions is a method which is called every time a button is clicked and is passed an options object which it uses to update this.state.options.
this.state = {
options: []
}
handleOptions(options){
let stat = this.state.options;
console.log(stat)
stat.push(options)
this.setState({ options: stat })
}
As you can see I'm logging the previous state options before calling this.setState to add it to the state. But for some reason the log output shows the updated state. It logs an empty array the first time as expected but after that it logs the updated state
I also tried passing a function to this.setState instead of an object, same result.
handleOptions(options){
this.setState((prevState) => {
let stat = prevState.options;
console.log(stat)
stat.push(options)
return {options: stat}
})
}
What am I missing???
push mutates original array. You could use concat instead.
handleOptions(options){
this.setState((prevState) => {
let stat = prevState.options;
console.log(stat)
return {options: stat.concat(options)}
})
}
As of
But for some reason the log output shows the updated state
Console tries to show you current object state. So if you modify an array using push you will see modified values. If you want to log the value at the very specific moment you could say log a copy console.log(stat.slice()) or a stringified representation console.log(JSON.stringify(stat))