Реактивный компонент не рендерится после изменений состояния Redux - PullRequest
2 голосов
/ 07 июня 2019

У меня есть следующая структура в моем состоянии Redux:

"equipment" : {
  "l656k75ny-r7w4mq" : {
    "id" : "l656k75ny-r7w4mq",
    "image" : "cam.png",
    "isChecked" : false,
    "list" : {
      "2taa652p5-mongp0" : {
        "id" : "2taa652p5-mongp0",
        "image" : "dst.png",
        "isChecked" : false,
        "name" : "Dst"
      },
      "y3ds1rspk-ftdg2t" : {
        "id" : "y3ds1rspk-ftdg2t",
        "image" : "flm.png",
        "isChecked" : false,
        "name" : "Flm"
      }
    },
    "name" : "EQ 1"
  },
  "lpbixy0f7-bmiwzl" : {
    "image" : "prm.png",
    "isChecked" : false,
    "list" : {
      "l40snp2y6-o7gudg" : {
        "image" : "tlp.png",
        "isChecked" : false,
        "name" : "Tlp"
      },
      "qmxf9bn3v-9x1nky" : {
        "image" : "prm.png",
        "isChecked" : false,
        "name" : "Prm"
      }
    },
    "name" : "EQ 2"
  }
}

У меня есть компонент Equipment (своего рода компонент более высокого порядка), в котором я подписываюсь на эту часть состояния и передаю ееданные в мой компонент CustomExpansionPanel:

class Equipment extends Component {
  //...different methods
  render() {
    const { data } = this.props;

    let content = error ? <p>Oops, couldn't load equipment list...</p> : <Spinner />;

    if ( data.length > 0 ) {
      content = (
        <Fragment>
          <CustomExpansionPanel data={data}/>
        </Fragment>
      );
    }

    return (
      <SectionContainer>
        {content}
      </SectionContainer>
    );
  }
}

const mapStateToProps = state => {
  return {
    data: state.common.equipment,
  };
};

connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(Equipment));

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

class CustomExpansionPanel extends Component {
  //... different methods
  render() {
    const { data } = this.props;

    return (
      <Fragment>
        {
          data.map((section, index) => {
            return (
              <ExpansionPanel defaultExpanded={false} key={section.id} disabled={!section.hasOwnProperty('list')} >
                <ExpansionPanelSummary expandIcon={<StyledIcon classes={{root: classes.expansionIcon}} />}>
                  <Avatar
                    alt={section.name}
                    src={images('./' + section.image)}
                    classes={{root: classes.avatar}}
                  />
                  <Typography variant='body1' classes={{root: classes.summaryText}}>{section.name}</Typography>
                </ExpansionPanelSummary>
                <ExpansionPanelDetails>
                  <CustomTable data={section} imageVariant={imageVariant} parentId={section.id} />
                </ExpansionPanelDetails>
              </ExpansionPanel>
            );
          })
        }
      </Fragment>
    );
  }
}

И в моем компоненте CustomTable я сопоставляю свойство списка полученного раздела и отображаю его данные.При нажатии на строку я создаю компонент Form с соответствующими полями и отображаю его для редактирования объекта элемента списка:

class CustomTable extends Component {
  //...local state
  //...some methods
  cellClickHandler = (object, index) => {
    this.setState({
      editedObject: object,
      editedObjectIndex: index
    }, () => {
      this.setFormData(object);
    });
  };

  handleSaveClicked = data => {
    data.id = this.state.editedObject.id;
    if(this.props.parentId) {
      data.parentId = this.props.parentId;
    }
    if ( this.state.editedObject && this.state.editedObject.list ) data.list = this.state.editedObject.list;
    this.props.onSaveItemEditClicked(this.props.updatedSection, data, this.state.editItemLevel, this.state.editedObjectIndex, () => {
      this.handleDialogState();
    });
  };

  componentWillMount() {
    if ( this.props.data.list ) {
      if ( Array.isArray(this.props.data.list) ) {
        this.setState({rows: [...this.props.data]});
      } else {
        let transformedData = [];
        for (let [key, value] of Object.entries(this.props.data.list)) {
          let obj = value;
          obj['id'] = key;
          transformedData.push(obj);
        }
        this.setState({
          rows: [...transformedData],
          sortedProperties: sortProperties(Object.keys(transformedData[0]))
        });
      }
    } else if (Array.isArray(this.props.data) && this.props.data.length) {
      this.setState({
        rows: [...this.props.data],
        sortedProperties: sortProperties(Object.keys(this.props.data[0]))
      });
    }

    if (this.props.parentId) {
      this.setState({editItemLevel: 'inner'})
    }
  }

  componentWillReceiveProps(nextProps, nextContext) {
    if ( nextProps.data.list ) {
      if ( Array.isArray(nextProps.data.list) ) {
        this.setState({rows: [...nextProps.data]});
      } else {
        let transformedData = [];
        for (let [key, value] of Object.entries(nextProps.data.list)) {
          let obj = value;
          obj['id'] = key;
          transformedData.push(obj);
        }
        this.setState({
          rows: [...transformedData],
          sortedProperties: sortProperties(Object.keys(transformedData[0]))
        });
      }
    } else if (Array.isArray(nextProps.data) && nextProps.data.length) {
      this.setState({
        rows: [...nextProps.data],
        sortedProperties: sortProperties(Object.keys(nextProps.data[0]))
      });
    }
  }

  render() {
    // Table, TableHead, TableRow, TableCell, TableFooter definitions...
    <FormDialog
      open={this.state.dialogOpen}
      title={this.state.dialogTitle}
      content={this.state.dialogContentText}
      handleClose={this.handleDialogState}
      formElements={this.state.formElements}
      action={this.handleSaveClicked}
      onCancelClicked={this.handleDialogState} />
  }
}

const mapStateToProps = state => {
  return {
    updatedSection: state.control.Update.updatedSection
  };
};

const mapDispatchToProps = dispatch => {
  return {
    onSaveItemEditClicked: (updatedSection, object, level, index, callback) => dispatch(commonActions.onSaveEdit(updatedSection, object, level, index, callback))
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(CustomTable));

Форма открывается, я могу изменить, например, имя объекта инажмите кнопку сохранения, которая вызывает метод onSaveItemEditClicked, который отправляет действие onSaveEdit.

onSaveEdit = (updatedSection, object, level='root', index, callback) => {
    return dispatch => {
      dispatch(controlActions.onSetSnackbarMessage(SnackbarMessages.PROCESS.UPDATING + ' \'' + capitalize(object.name) + '\''));
      dispatch(controlActions.DBProcessStart());
      let parentId = null;
      let url = '/' + updatedSection + '/' + object.id;
      if (level === 'inner') {
        url = '/' + updatedSection + '/' + object.parentId + '/list/' + object.id;
        parentId = object.parentId;
        delete object.parentId;
      }
      updateData(url, object)
        .then(response => {
          dispatch(controlActions.onSetSnackbarMessage('\'' + capitalize(object.name) + '\' ' + SnackbarMessages.SUCCESS.UPDATED_SUCCESSFULLY));
          dispatch(controlActions.DBProcessSuccess());
          if (parentId) {
            object.parentId = parentId;
          }
          dispatch(this[saveEditSuccess](updatedSection, object, level, index));
          if (callback != null) {
             callback();
          }
        })
        .catch(error => {
          console.error('onSaveEdit error', error);
        });
    }
  };
  [saveEditSuccess] = (updatedSection, object, level='root', index) => {
    return {
      type: CommonCommands.UPDATE.SUCCESS,
      section: updatedSection,
      object: object,
      level: level,
      index: index
    };
  };

И, наконец, это мой редуктор:

const onUpdateSuccess = (state, action) => {
  switch (action.level) {
    case 'root':
      let section = [...state[action.section]];
      section = [
        ...section.slice(0, action.index),
        action.object,
        ...section.slice(action.index + 1),
      ];
      return updateObject(state, {
        [action.section]: section
      });
    case 'inner':
      console.log('inner update success', action);
      section = [...state[action.section]];
      let parentId = action.object.parentId;
      let subsectionIndex = state[action.section].findIndex(member => member.id === parentId);
      delete action.object.parentId;
      let updatedObject = {...section[subsectionIndex].list[action.object.id], ...action.object};
      section[subsectionIndex].list[action.object.id] = updatedObject;
      return updateObject(state, {
        [action.section]: [...section]
      });
    default:
      return state;
  }
};

Это метод updateObject:

const updateObject = (oldObject, updatedProperties) => {
  return {
    ...oldObject,
    ...updatedProperties
  };
};

Теперь, если я проверяю состояние с помощью инструмента Debug Redux, я вижу новое имя объекта, я вижу, что состояние действительно меняется.Но компонент CustomTable не перерисовывается, я все еще вижу старое имя.

Точно такая же методология работает для меня в любом другом случае.Если у меня есть массив или объект в свойстве состояния, и я отображаю его в той же CustomTable - я могу отредактировать его и сразу увидеть результаты (следовательно, у меня есть эти «корневые» и «внутренние» разделения).Но не в этом случае.

Я пытался использовать Object.assign({}, state, {[action.section]:section}); вместо updateObject метода, но это также не помогло.

Любые идеи, как заставить этот компонент перерисовываться с помощьюизменились свойства?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...