简体   繁体   中英

REACT REDUX: Update value in deep nested object

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);

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.

https://redux.js.org/recipes/structuringreducers/immutableupdatepatterns#correct-approach-copying-all-levels-of-nested-data

and https://redux.js.org/recipes/structuringreducers/immutableupdatepatterns#inserting-and-removing-items-in-arrays

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

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.

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM