Как правильно привязать вложенные объекты к текстовым вводам с помощью redux

#javascript #reactjs #redux #react-redux

#javascript #reactjs #redux #реагировать-redux

Вопрос:

У меня есть объект класса Entities, который добавляет себя в редуктор сущностей, используя идентификатор в качестве своего ключа. В свойствах другого класса мне нужно использовать каждое из свойств объекта entity для создания текстовых вводов.

Проблема, с которой я сталкиваюсь, заключается в том, что когда я обновляю свое текстовое поле ввода, оно принимает первый введенный мной ключ и преобразует его в значение, но каждый раз переписывает полное значение, поэтому я всегда получаю значение в один символ.

У меня есть класс Entities:

 import React from 'react';
import { connect } from 'react-redux';
import { updateSelected, addEntity } from '../actions';

class Entity extends React.PureComponent {
  constructor(props) {
    super(props);
    this.identityContainer = {
      id: "abcdef",
      type: "person",
      properties: {
        name: {
          label: "Name", 
          type: "string", 
          value: "", 
          optional: true
        },
        favorite_icecream: {
          label: "Favorite Ice Cream", 
          type: "string", 
          value: "", 
          optional: true
        }
      }
    }
    this.props.addEntity(this.identityContainer);
  }
  handleSelected() {
    this.props.updateSelected(this.identityContainer.id)
  }
  render() {
    return (
        <div onMouseUp={() => this.handleSelected()}></div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    selectedEntity: state.selectedEntity
  }
}
const mapDispatchToProps = () => {
  return {
    updateSelected,
    addEntity
  }
}
export default connect(mapStateToProps, mapDispatchToProps())(Entity);
 

Теперь вот мой класс PropertiesWindow:

 import React, { Component } from 'react';
import { connect } from 'react-redux';
import { updateProperty } from '../actions';

class PropertiesWindow extends Component {
    get selectedEntity() {
        return this.props.entities[this.props.selectedEntity];
    }
    handleUpdateEntity(id, name, value) {
        this.props.updateProperty(id, name, value);
    }
    createListItems() {
        for (const [name, property] of Object.entries(this.selectedEntity.properties)) {
            return (
                <li key={this.props.selectedEntity   property.name} className="text-input-container">
                    <label htmlFor={this.props.selectedEntity}>{property.label}</label>
                    <input 
                        type="text" 
                        id={this.props.selectedEntity} 
                        name={name}
                        value={property.value}
                        onChange={e => this.handleUpdateEntity(this.selectedEntity.id, name, e.target.value)}
                    />
                </li>
            )
        }
    }
    render() {
        return (
            <ul>
                {this.createListItems()}
            </ul>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        selectedEntity: state.selectedEntity,
        entities: state.entities
    }
}
const mapDispatchToProps = () => {
  return {
    updateProperty
  }
}

export default connect(mapStateToProps, mapDispatchToProps())(PropertiesWindow);
 

Действия:

 export const updateSelected = (id) => {
    return {
        type: 'SELECTED_ENTITY_UPDATED',
        payload: id
    }
}
export const addEntity = (entity) => {
    return {
        type: 'ENTITY_ADDED',
        payload: {...entity}
    }
}
export const updateProperty = (id, property, value) => {
    return {
        type: 'PROPERTY_CHANGED',
        payload: {id, property, value}
    }
}
 

редукторы:

 import {combineReducers} from 'redux';

export const selectedEntityReducer = (state = null, action) => {
    switch (action.type) {
        case 'SELECTED_ENTITY_UPDATED':
            return action.payload;
        default: 
            return state;
    }
}

export const entitiesReducer = (state = {}, action) => {
    switch (action.type) {
        case 'ENTITY_ADDED':
            state[action.payload.id] = action.payload;
            return state;
        case 'PROPERTY_CHANGED':
            state[action.payload.id].properties[action.payload.property] = action.payload.value;
            return state;
        default: return state;
    }
}

export default combineReducers({
    selectedEntity: selectedEntityReducer,
    entities: entitiesReducer
});
 

После ввода в текстовое поле я регистрирую это из action.полезная нагрузка:
updateProperty: Object { id: "abcdef", property: "name", value: "a" }
updateProperty: Object { id: "abcdef", property: "name", value: "s" }
updateProperty: Object { id: "abcdef", property: "name", value: "d" }
updateProperty: Object { id: "abcdef", property: "name", value: "f" }

Если бы кто-нибудь мог помочь мне понять, что я делаю неправильно, я был бы очень признателен. Я пытался решить эту проблему множеством разных способов, в том числе используя массив для свойств и массив для сущностей вместо объектов. Ничто из того, что я пробовал, не имело никакого значения.

РЕДАКТИРОВАТЬ:
я создал codesandbox здесь: https://codesandbox.io/s/gracious-leakey-vzio6

Комментарии:

1. В классе PropertiesWindow у вас есть onChange={e => this.handleUpdateEntity(this.selectedEntity.id, name, property.value)} , я предполагаю, что это вызывает проблему при повторном использовании значения свойства. Попробуйте переключиться property.value e.target.value , чтобы вы могли получить фактическое значение во входных данных

2. Я смотрю со своего телефона, так что трудно чем-то помочь, но синтаксис здесь выглядит неправильно: handleUpdateEntity(this.props.updateProperty(id, name, value) { на самом деле вы можете удалить всю эту функцию и вызвать prop непосредственно в обработчике on change, как const { updateProperty } = this.props; тогда onChange={e => updateProperty(id, name, e.target.value)}

3. Кроме key того, то, что у вас есть на входе, похоже, не уникально, поскольку это объект, и вы используете его в цикле, сделайте ключ именем, свойством, индексом или чем-то уникальным, что не изменится

4. Хорошо, просто убедитесь, что ключ уникален для каждого ввода, а не только для каждого объекта, и что ключ не меняется при каждом рендеринге.

5. Если вам все еще нужна помощь в этом, пожалуйста, добавьте пример codesandbox, чтобы нам было легче его отлаживать.

Ответ №1:

Попробуйте отправлять фактическое входное значение каждый раз, когда оно изменяется, например:

 <input 
  type="text" 
  id={this.props.selectedEntity} 
  name={name}
  value={property.value}
  onChange={e => this.handleUpdateEntity(this.selectedEntity.id, name, e.target.value)}
 />
 

Измененная строка — это:

 onChange={e => this.handleUpdateEntity(this.selectedEntity.id, name, e.target.value)
 

Комментарии:

1. Я надеюсь, что вы действительно пропустили мой комментарий и не используете его 🙂

2. Я действительно заметил, что набрал это таким образом, и исправил это. Однако моя проблема все еще остается нерешенной :/

3. Извините, Ренан, наш комментарий / ответ, должно быть, был опубликован одновременно, потому что сообщение было пустым, когда я впервые попал на страницу.

Ответ №2:

Итак, после нескольких дней доработки я обнаружил, что проблема заключается в правильном изменении значений вложенных объектов. В моем коде я попытался изменить значения вложенного properties объекта, обратившись к нему с помощью вычисляемых имен свойств, таких как

 state[action.payload.id].properties[action.payload.property] = action.payload.value
 

Однако я обнаружил, что это не обновит состояние; или, если это произойдет, это будет сделано таким образом, чтобы не запускать повторный рендеринг. Во многих других вопросах я видел, что переменная spread должна использоваться для возврата совершенно нового объекта, который затем запускает повторный рендеринг. Возьмем этот код для примера:

 case ENTITY_UPDATED:
    const {id, name, value} = action.payload;
    return {
        ...state,
        [id]: {
            ...state[id],
            properties: {
                ...state[id].properties,
                [name]: {
                    ...state[id].properties[name],
                    value: value
                }
            }
        }
    }
 

Это способ изменения состояния, который необходимо было изменить, чтобы redux зарегистрировал изменения. Я не уверен на 100%, почему это так, но это так. Если кто-то хорошо разбирается в этом, пожалуйста, свяжитесь с нами и прокомментируйте!