SetState in an array with React spread operator - javascript

I have a value in my react state that looks like so:
appointmentsData = {
"05-01-2018": [
{
id: "1",
friendly: "9:00am - 10:00am",
openDateTime: "9:00am",
closeDateTime: "10:00am"
}
]
};
I have a new piece of data:
const newAppt = {"06-30-2018":[]}
How do I addon to my data in the state? I tried
this.setState({ ...this.state.appointmentsData, ...newAppt});
It seems to no be adding onto state though. I get back the same value in the render as before (appointmentData before spread). The newAppt never gets added to the state.
I am trying to get my states desired output to be:
appointmentsData = {
"05-01-2018": [
{
id: "1",
friendly: "9:00am - 10:00am",
openDateTime: "9:00am",
closeDateTime: "10:00am"
}
],
"06-30-2018": []
};

Many ways to do it, really. Here's one:
this.setState(prevState => (
{appointmentsData: {...prevState.appointmentsData}, newAppt}
));
Though your approach should work too, if you just add the appointmentsData: first. Like so:
this.setState({appointmentsData: {...this.state.appointmentsData, ...newAppt}});
This will spread all the previous values into a new object, plus the one you want to add.
I personally always use the functional way of setting the state when the new one depends on a previous state value, but your way is fine here too.

You should not use spread operator on newAppt. Just go with:
this.setState({ ...this.state.appointmentsData, newAppt});. It seems to me that the spread operator on newAppt is not getting any data for the const.

Related

How can I structure my data better in the redux state?

I'm new to this so it might be a very beginner question but I hope there might be a solution to this for the people who are good.
songData() here returns an array of lots of song objects.
That's why the [0] here in currentSong: { currentSongData: songData()[0] },
This is my redux state:
const initState = {
allSongs: songData(),
currentSong: { currentSongData: songData()[0] },
isPlaying: false,
isLibraryOpen: false,
songTimer: [
{
currentTime: 0,
duration: 0,
},
],
};
Whenever I have to access anything in the currentSong, I have to write something like:
currentSong.currentSongData.name
or if I define the currentSong as currentSong: [songData()[0]], then like:
currentSong[0].name
Is there a better way to define the redux state where I don't have to write such long things when I access the data?
I want to access it by writing
currentSong.name
BTW this is the structure of the song object.
{
name: 'Cold Outside',
cover: 'https://chillhop.com/wp-content/uploads/2020/09/09fb436604242df99f84b9f359acb046e40d2e9e-1024x1024.jpg',
artist: 'Nymano',
},
You can use property spread notation
currentSong: { ...songData()[0] }
I would favor to store currentSong's key id which you could extract then its data content from allSongs. otherwise, you would be duplicating data something you better avoid. in this way, at initial state you would have currentSongId: songData()[0].id
so how would you handle that at your functions? you can use useSelector. this function allows you to filter your data so you consume it properly. at your function component you would extract like:
// at your component function
const selectedSong = useSelector(state => state.allSongs.filter(({ id }) => id === state.currentSongId))
if you read further below at useSelector docs, you could go a step further and memoize its value to avoid unnecessary rerenders in combination with another library reselect.

Do I need to use the spread operator when using useState hook on object when updating?

I just started learning about hooks, and according to the official docs on Using Multiple State Variables, we find the following line:
However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it.
So, if I understand correctly, this mean I don't need to use the spread operator for updating the state?
You still don't want to mutate state. So if your state is an object, you'll want to create a new object and set with that. This may involve spreading the old state. For example:
const [person, setPerson] = useState({ name: 'alice', age: 30 });
const onClick = () => {
// Do this:
setPerson(prevPerson => {
return {
...prevPerson,
age: prevPerson.age + 1
}
})
// Not this:
//setPerson(prevPerson => {
// prevPerson.age++;
// return prevPerson;
//});
}
That said, using hooks you often no longer need your state to be an object, and can instead use useState multiple times. If you're not using objects or arrays, then copying is not needed, so spreading is also not needed.
const [name, setName] = useState('alice');
const [age, setAge] = useState(30);
const onClick = () => {
setAge(prevAge => prevAge + 1);
}
What it means is that if you define a state variable like this:
const [myThings, changeMyThings] = useState({cats: 'yes', strings: 'yellow', pizza: true })
Then you do something like changeMyThings({ cats: 'no' }), the resulting state object will just be { cats: 'no' }. The new value is not merged into the old one, it is just replaced. If you want to maintain the whole state object, you would want to use the spread operator:
changeMyThings({ ...myThings, cats: 'no' })
This would give you your original state object and only update the one thing you changed.

useState updating state child array object not working

I have this state in my React component-->
const [employeeState, setEmployee] = useState({
"empName": null,
"Age": null,
"depts": [], //depts is an array of objects
"groups": [] //group is an array of objects
});
I have a separate state for depts and groups like follows -->
const depts = {
"name": "some name",
"code": "s123",
}
const [deptState, setDeptState] = useState([
{ ...depts},
]);
same is for groups too....
Now when I am trying to set the employee state on some btn click like below it's not updating and keeping depts and groups property as it is empty -->
const deptsLst = [...deptState];
const groupsLst = [...groupstate];
console.log(depsLst); // this will print the results as expected
console.log(groupsLst); // this will print the results as expected
setEmployee({
...employeeState,
['depts']: deptsLst ,
['groups']: groupsLst ,
})
console.log(employeeState);// this will show depts and groups as empty only
I am new to this spread variable copying concept. What am I doing wrong here?
You state that "I am new to this spread variable copying concept"...not only are you new to the spread operation but your code demonstrates that you do not have a basic understanding of it I think. You are using spread operators on top of spread operators on top of computed property names, all for no reason.
You are also using React state to manage the employee object and then using React state to manage Array properties on the employee object. If you are already performing state management on the employee object, why maintain state of individual properties?
Ok a couple breakdowns of your code:
const depts = {
"name": "some name",
"code": "s123",
}
const [deptState, setDeptState] = useState([
{ ...depts},
]);
Above you clone an object for no reason.
With React state management you only need to clone objects to trigger a render event. A clone creates a new object with a new reference in memory, will "react" to object reference changes and trigger a state change event. React is not capable of watching every property of a complex object, especially array elements to see if a value has changed.
The below code will suffice, you are initializing state, you do not need to clone:
const [deptState, setDeptState] = useState([{
"name": "some name",
"code": "s123",
}]);
Next this code:
setEmployee({
...employeeState,
['depts']: deptsLst ,
['groups']: groupsLst ,
})
Here you are attempting to use the spread operator to clone the "employeeState" object and update new values for the depts property key and groups property key. You use "Computed Property Names' for property keys for no reason.
This is sufficient:
setEmployee({
...employeeState,
depts: deptsLst ,
groups: groupsLst
})
You also create unnecessary state:
const [deptState, setDeptState] = //..rest of code omitted
Here you do not realize React state changes happen async:
setEmployee({
...employeeState,
['depts']: deptsLst ,
['groups']: groupsLst ,
})
console.log(employeeState);
The above code actually has changed the employeeState, just not be the time of your console.log(employeeState);
Ok, so to start I have created a CodePen for you to understand how state changes.
When you want to change state for this employee object of yours you simply need to change the object property values or add new property values and then clone the employee object to change its reference and than call setState with the clone, consider this object state:
const [employee, setEmployee] = React.useState({
"empName": 'Jane Doe',
"Age": 33,
"depts": [
{
"name": "some name",
"code": "s123",
}
]
});
Here I am adding a department to the employee, note I only clone the employee object to trigger React to detect the overall object reference change, React does not know I changed property depts array but the new state does have my new value I pushed on the department array:
function exampleUpdatingEmployee() {
employee.depts.push({
"name": "some new dept",
"code": "876",
});
setEmployee({ ...employee });
}
You can do:
setEmployee({
...employeeState,
depts: deptsLst,
groups: groupsLst
})
to update your employeeState. You probably won't see the update with the console.log you have right after that since it will execute before the state updates. You can verify that the employeeState updated by using a useEffect function like this:
useEffect(() => {
console.log(employeeState)
}, [employeeState])
The spread operator is the ES6 way of copying arrays without passing a reference to your copy so you can modify one array without those changes showing up in the other array. You can read into that more here: Reference And Copying Objects & Arrays.
if depts and group are arrays of objects, you should use spread operator in this way to copy values:
setEmployee({
...employeeState,
depts: [...deptState],
groups: [...groupstate]
});
so if:
const employeeState = {status: 'hungry'};
const deptState = ['one', 'two'];
const groupstate = ['y', 'k', 'o'];
then the first code, after spread, is like:
setEmployee({
status: 'hungry',
depts: ['one', 'two'],
groups: ['y', 'k', 'o']
});

How to update the value of a single property within a state object in React.js?

So I have the following object structure:
const SamplePalette = {
id: 1,
name: "Sample Palette",
description: "this is a short description",
swatches: [
{
val: "#FF6245",
tints: ["#FFE0DB", "#FFA797"],
shades: ["#751408", "#C33F27"]
},
{
val: "#FFFDA4",
tints: ["#FFFFE1"],
shades: ["#CCCB83"]
},
{
val: "#BFE8A3",
tints: ["#E7FFD7"],
shades: ["#95B77E"]
}
]
}
Let's imagine that this object is managed by the state of my app like this:
this.state = {
currentPalette: SamplePalette,
}
My question is how would I go about updating the val property of a given swatch object in the swatches array? Or more generally - how do I only update pieces of this object?
I tried using the update helper as well as to figure out how Object.assign() works, however I've been unsuccessful and frankly can't really grasp the syntax by just looking at examples.
Also, since I'm going to be modifying this object quite a lot, should I look into maybe using Redux?
[EDIT]
I tried #maxim.sh suggestion but with no success:
this.setState(
{ currentPalette: {...this.state.currentPalette,
swatches[0].val: newValue}
})
Consider you have new new_swatches
I think the clearer way is to get array, update it and put back as:
let new_swatches = this.state.currentPalette.swatches;
new_swatches[0].val = newValue;
this.setState(
{ currentPalette:
{ ...this.state.currentPalette, swatches: new_swatches }
});
Also you have : Immutability Helpers or https://github.com/kolodny/immutability-helper
Available Commands
{$push: array} push() all the items in array on the target.
{$unshift: array} unshift() all the items in array on the target.
{$splice: array of arrays} for each item in arrays call splice() on the target with the parameters provided by the item.
{$set: any} replace the target entirely.
{$merge: object} merge the keys of object with the target.
{$apply: function} passes in the current value to the function and updates it with the new returned value.

Replace object in array on react state

This question might fall a little on the side of a "best practice" question, but please bear with me.
Here is a portion of my state:
this.state = {
typeElements: {
headers: [
{
name: "h1",
size: 70,
lineHeight: 1.25,
kearning: 0,
marginAfter: 0
}, {
name: "h2",
size: 70,
lineHeight: 1.25,
kearning: 0,
marginAfter: 0
}, {
name: "h3",
size: 70,
lineHeight: 1.25,
kearning: 0,
marginAfter: 0
}...
What I need to do is REPLACE the object at a given index on the headers array.
I don't know how to do that with the setState method as in this.setState(headers[1] = {obj}) - but that's obviously invalid. My current method is creating a new array and clobbering the old one like this:
_updateStyle(props) {
let newState = Object.assign({}, this.state)
newState.typeElements.headers[props.index] = props
this.setState(newState)
};
For my small hacky project I guess it's OK but I feel like this is super heavy handed and would quickly lead to performance issues at any kind of scale.
Updated: since this answer still gets upvotes, be aware that the previous answer below is outdated with modern JavaScript and React. The "update" addon is now legacy and "immutability-helper" can be used instead.
The React docs also mention why immutability is important so avoid mutating state. For immutable updates you can use Object.assign() or spread syntax which needs to be done for every level of nesting, like in this example the nested headers object and its array elements. In this particular example we can use the array index as key so it's possible to also use the spread operator to make a shallow clone of the array and assign a new object as value at given index in the cloned array.
_updateStyle (props) {
const { typeElements } = this.state;
const updatedHeaders = [...typeElements.headers];
updatedHeaders[props.index] = props;
this.setState({
...this.state,
typeElements: {
...typeElements,
headers: updatedHeaders
}
));
}
Another solution which doesn't require the spread syntax and is needed if we are not using the array index to find the object we want to replace, is using array.map to create a new array and returning the new object instead of the old one at given index.
const updatedHeaders = typeElements.headers.map((obj, index) => {
return index === props.index ? props : obj;
});
Similar examples in the Redux docs also explain "immutable update patterns".
React has some immutability helpers for this, which is explained in
the docs: https://facebook.github.io/react/docs/update.html
In your case you could use the $splice command to remove one item and
add the new one at given index, for example:
_updateStyle (props) {
this.setState(update(this.state.typeElements,
{ $splice: [[props.index, 1, props]] }
));
}
Offering a better explanation of how to accomplish this.
First, find the index of the element you're replacing in the state array.
Second, update the element at that index
Third, call setState with the new collection
import update from 'immutability-helper';
// this.state = { employees: [{id: 1, name: 'Obama'}, {id: 2, name: 'Trump'}] }
updateEmployee(employee) {
const index = this.state.employees.findIndex((emp) => emp.id === employee.id);
const updatedEmployees = update(this.state.employees, {$splice: [[index, 1, employee]]}); // array.splice(start, deleteCount, item1)
this.setState({employees: updatedEmployees});
}
use immutability-helper
you can find nice examples there
Object.Assign uses shallow copy, not deep copy.
Please be aware that in your example Object.assign({}, this.state) copies only links to the nearest children of the state, i.e. to typeElements, but headers array is not copied.
There is a syntactic sugar ... in ES6 for Object.Assign, Babel has the special addon transform-object-rest-spread.
let newHeaders = { ...this.state.typeElements.headers};
newHeaders[index] = props.Something;
let newState = {...state, typeElements: { newHeaders }};

Categories

Resources