Reputation: 41
I have an object of class Entities that adds itself to an entities reducer using an id as its key. In another class Properties, I need to use each of the entity object's properties to create text inputs.
The issue I'm having is that when I update my input textbox, it takes the first key I type and makes that the value, but it rewrites the full value every time so I always end up with a one character value.
I have a class 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);
Now here is my PropertiesWindow class:
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);
actions:
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}
}
}
reducers:
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
});
Upon typing into the textbox I log this from action.payload:
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" }
If someone could help me understand what I am doing wrong I would really appreciate it. I have been trying to solve this issue many different ways, including using an array for properties and an array for entities instead of objects. Nothing I have tried has made any difference.
EDIT:
I have created a codesandbox here:
https://codesandbox.io/s/gracious-leakey-vzio6
Upvotes: 1
Views: 191
Reputation: 41
So after days of reworking this, I have discovered that the issue is properly changing the values of nested objects. In my code, I tried to change the values of the nested properties
object by accessing it with computed property names like
state[action.payload.id].properties[action.payload.property] = action.payload.value
However I found that this will not update the state; or if it does, it does so in a way that does not trigger a re-render. In many other questions I have seen that the spread variable must be used to return an entirely new object, which then triggers re-renders. Take this code for example:
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
}
}
}
}
This is the way the state needed to be changed for redux to register the changes. I am not 100% sure why this is the case but it is. If anyone has a firm grasp on it please reach out and comment!
Upvotes: 0
Reputation: 1047
Try sending the actual input value every time that it changes, like this:
<input
type="text"
id={this.props.selectedEntity}
name={name}
value={property.value}
onChange={e => this.handleUpdateEntity(this.selectedEntity.id, name, e.target.value)}
/>
The line changed is this one:
onChange={e => this.handleUpdateEntity(this.selectedEntity.id, name, e.target.value)
Upvotes: 1