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.
Related
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.
I'm very new to Redux, and confused as to how to update nested state.
const initialState = {
feature: '',
scenarios: [{
description: '',
steps: []
}]
}
I know that to just push to an array in an immutable way, we could do,
state = {
scenarios: [...state.scenarios, action.payload]
}
And to push into a specific attribute, as this SO answer suggests
How to access array (object) elements in redux state, we could do
state.scenarios[0].description = action.payload
But my question is, how would we access a particular attribute in an object array without mentioning the index? is there a way for us to push it to the last empty element?
All suggestions are welcome to help me understand, thank you in advance :)
Redux helps to decouple your state transformations and the way you render your data.
Modifying your array only happens inside your reducers. To specify which scenario's description you want to modify is easy to achieve by using an identifier. If your scenario has in id, it should be included in your action, e.g.
{
"type": "update_scenario_description",
"payload": {
"scenario": "your-id",
"description": "New content here"
}
}
You can have a reducer per scenario. The higher level reducer for all scenarios can forward the action to the specific reducer based on the scenario id, so that only this scenario will be updated.
In your ui, you can use the array of scenarios and your scenario id to render only the specific one you're currently viewing.
For a more detailed explanation, have a look at the todo example. This is basically the same, as each todo has an id, you have one reducer for all todos and a specific reducer per todo, which is handled by it's id.
In addition to the accepted answer, I'd like to mention something in case someone still wants to "access a particular attribute in an object array without mentioning the index".
'use strict'
const initialState = {
feature: '',
scenarios: [{
description: '',
steps: []
}]
}
let blank = {}
Object.keys(initialState.scenarios[0]).map(scene => {
if (scene === 'steps'){
blank[scene] = [1, 2]
} else {
blank[scene]=initialState.scenarios[0][scene]
}
})
const finalState = {
...initialState,
scenarios: blank
}
console.log(initialState)
console.log(finalState)
However, if scenarios property of initialState instead of being an object inside an array, had it been a simple object like scenarios:{description:'', steps: []}, the solution would have been much simpler:
'use strict'
const initialState = {
feature: '',
scenarios: {
description: '',
steps: []
}
}
const finalState = {
...initialState,
scenarios: {
...initialState.scenarios, steps: [1, 2, 4]
}
}
console.log(initialState)
console.log(finalState)
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.
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 }};
What's the best/correct way to update a nested array of data in a store using redux?
My store looks like this:
{
items:{
1: {
id: 1,
key: "value",
links: [
{
id: 10001
data: "some more stuff"
},
...
]
},
...
}
}
I have a pair of asynchronous actions that updates the complete items object but I have another pair of actions that I want to update a specific links array.
My reducer currently looks like this but I'm not sure if this is the correct approach:
switch (action.type) {
case RESOURCE_TYPE_LINK_ADD_SUCCESS:
// TODO: check whether the following is acceptable or should we create a new one?
state.items[action.resourceTypeId].isSourceOf.push(action.resourceTypeLink);
return Object.assign({}, state, {
items: state.items,
});
}
Jonny's answer is correct (never mutate the state given to you!) but I wanted to add another point to it. If all your objects have IDs, it's generally a bad idea to keep the state shape nested.
This:
{
items: {
1: {
id: 1,
links: [{
id: 10001
}]
}
}
}
is a shape that is hard to update.
It doesn't have to be this way! You can instead store it like this:
{
items: {
1: {
id: 1,
links: [10001]
}
},
links: {
10001: {
id: 10001
}
}
}
This is much easier for update because there is just one canonical copy of any entity. If you need to let user “edit a link”, there is just one place where it needs to be updated—and it's completely independent of items or anything other referring to links.
To get your API responses into such a shape, you can use normalizr. Once your entities inside the server actions are normalized, you can write a simple reducer that merges them into the current state:
import merge from 'lodash/object/merge';
function entities(state = { items: {}, links: {} }, action) {
if (action.response && action.response.entities) {
return merge({}, state, action.response.entities);
}
return state;
}
Please see Redux real-world example for a demo of such approach.
React's update() immutability helper is a convenient way to create an updated version of a plain old JavaScript object without mutating it.
You give it the source object to be updated and an object describing paths to the pieces which need to be updated and changes that need to be made.
e.g., if an action had id and link properties and you wanted to push the link to an array of links in an item keyed with the id:
var update = require('react/lib/update')
// ...
return update(state, {
items: {
[action.id]: {
links: {$push: action.link}
}
}
})
(Example uses an ES6 computed property name for action.id)