Reputation: 357
I am trying to update a value stored in a deep nested object. It contains many pieces of information and the schema is fixed. I am trying to copy the object then return the object with update value onChange from an input. However I am unable to successfully correctly copy the full tree and return the updated content.
DEMO: https://codesandbox.io/s/4j7x8jlk9w
the object looks like:
content: {
label: "Label",
templates: [
{
name: "example",
type: "the type",
items: [
{
key: "key1",
properties: {
text: {
label: "The Label 1",
value: "The Value 1"
},
color: {
label: "Color",
value: "#123"
}
}
},
{
key: "key2",
properties: {
text: {
label: "The Label 2",
value: "The Value 2"
},
color: {
label: "Color",
value: "#456"
}
}
}
]
}
]
}
The Reducer:
case "UPDATE_VALUE":
const content = state.content.templates[state.templateKey].items[
state.itemKey
].properties.text.value =
action.value;
return { ...state, content };
default:
return state;
}
The Component:
import React, { PureComponent } from "react";
import { connect } from "react-redux";
import { updateValue } from "./actions";
class Page extends PureComponent {
render() {
const { content, templateKey, itemKey } = this.props;
return (
<div>
<h1
style={{
color:
content.templates[templateKey].items[itemKey].properties.color
.value
}}
>
{content.templates[templateKey].items[itemKey].properties.text.value}
</h1>
<input
name={content.templates[templateKey].items[itemKey].key}
value={
content.templates[templateKey].items[itemKey].properties.text.value
}
onChange={e => this.props.updateValue(e.target.name, e.target.value)}
/>
</div>
);
}
}
const mapStateToProps = state => ({
content: state.content,
templateKey: state.templateKey,
itemKey: state.itemKey
});
const mapDispatchToProps = dispatch => ({
updateValue: (key, value) => dispatch(updateValue(key, value))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Page);
Upvotes: 2
Views: 5503
Reputation: 2590
With deeply nested data like this, you can use the spread syntax at each depth of the tree until you arrive at the thing you want to change. For arrays, you can use slice
to create a copy of the array without mutating it.
will be resources to help you understand this better.
Let's assume your reducer is given an index for which template to update, and an index for which item to update within that template. Your code might look like this:
return {
...state,
templates: [
...state.templates.slice(0, templateIndex),
{
...state.templates[templateIndex],
items: [
...state.templates[templateIndex].items.slice(0, itemIndex),
{
...state.templates[templateIndex].items[itemIndex],
value: action.value
},
...state.templates[templateIndex].items.slice(itemIndex)
]
},
...state.templates.slice(templateIndex)
]
}
As you can see, it gets pretty messy when you're dealing with nested data like this. It's recommended that you normalize nested data to make your reducers have to do less work to find the thing to change.
Here's your updated codesandbox: https://codesandbox.io/s/w77yz2nzl5
Upvotes: 4
Reputation: 49
first of all , you are not returning the proper content object , as you are returning the value of the update , so you should update that :
const content = state.content.templates[state.templateKey].items[
state.itemKey
].properties.text.value =
action.value;
with :
const content = state.content;
content.templates[state.templateKey].items[
state.itemKey
].properties.text.value =
action.value;
The key would be either returning a new reference for content , as you are applying a selector in map state to props on the content itself ( or , as a much better solution , composing such reducer to contain multiple reducers) , ie :
const content = {...state.content};
content.templates[state.templateKey].items[
state.itemKey
].properties.text.value =
action.value;
or applying your selector on the values you want (ie more granular selectors)
Upvotes: -1
Reputation: 146
Have you tried using the spread operating to populate a new object with the contents of the current state of the old nested object, then you add the new updated values after? That way the fields you don't change remain the same. This isn't exact, but what I would do in your case is create a method on page.js like:
createNewValue = (e) => {
const { content, templateKey, itemKey } = this.props;
let newValues = {...content }
newValues.templates[templateKey].items[itemKey].properties.text.value = e.target.value
this.props.updateValue(newValues)
}
Then in your updateValue action creator you just pass in the new content object that retains the old values that you aren't updating, and includes the new value for the change you are making. You would fire off this method on page.js in your onChange handler.
Upvotes: -1