React, Redux: обновление состояния и повторный рендеринг дочерних элементов

Сценарий, с которым я сталкиваюсь, заключается в том, что у меня есть массив заданий, хранящихся в состоянии Redux. У меня есть контейнер, который получает доступ к данным с подключением Redux и использует параметр из this.props.params, чтобы затем найти подходящее задание, а затем передать его детям в качестве опоры.

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

Мой вопрос: как лучше всего (Redux) справиться с этим?

Поэтому, если я изменю JobShow, включив в него jobs={jobs}, он будет повторно отображаться по мере обновления заданий. Похоже, это указывает на то, что состояние не мутирует. Я также могу подтвердить, что сам контейнер повторно отображает и даже повторно запускает renderView(), но не выполняет повторный рендеринг JobShow.

Контейнер:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import JobShow from './job_show';


class JobContainer extends Component {
  constructor(props){
    super(props);
  }

  renderView(job, customer){
    let viewParam = this.props.params.view;
    switch(viewParam){
      case "edit": return <JobEdit customer={customer} job={job}/>
      case "notes": return <JobShow customer={customer} activeTab={"notes"} job={job}/>
      case "quote": return <JobShow customer={customer} activeTab={"quote"} job={job}/>
      case "invoice": return <JobShow customer={customer} activeTab={"invoice"} job={job}/>
      default: return <JobShow customer={customer} activeTab={"notes"} job={job}/>
    }
  }
  render() {
    let { jobs, customers } = this.props;

    if (jobs.length < 1 || customers.length < 1){
      return <div>Loading...</div>;
    }
    let job = jobs.find(jobbie => jobbie.displayId == this.props.params.jobId);
    let customer = customers.find(customer => customer._id.$oid == job.customers[0]);

    return (
      <div className="rhs">
        {this.renderView(job, customer)}
      </div>
    );
  }
}
JobContainer.contextTypes = {
  router: React.PropTypes.object.isRequired
};
function mapStateToProps(state) {
  return  { 
    jobs: state.jobs.all,
    customers: state.customers.all
  };
}

export default connect(mapStateToProps, actions )(JobContainer);

Фрагмент редуктора:

import update from 'react-addons-update';
import _ from 'lodash';

export default function(state= INITIAL_STATE, action) {
  switch (action.type) {
    case FETCH_ALL_JOBS:     
      return { ...state, all: action.payload, fetched: true };

    case UPDATE_JOB_STATUS:

      let newStatusdata = action.payload;

      let jobToUpdate = state.all.find(jobbie => jobbie.displayId == newStatusdata.displayId);
      jobToUpdate.status = newStatusdata.newStatus;
      jobToUpdate.modifiedAt = newStatusdata.timeModified;

      let updatedJobIndex = _.indexOf(state.all, state.all.find(jobbie => jobbie.displayId == newStatusdata.displayId));
      let updatedState = update(state.all, {$merge: {[updatedJobIndex]: jobToUpdate}});

      return { ...state, all: updatedState}

person Dom Hede    schedule 27.06.2016    source источник
comment
Если ничего другого, похоже, что вы мутируете свое состояние строками jobToUpdate.status = ..... Случайная мутация почти всегда является причиной того, что компоненты не перерисовываются. См. redux.js.org/docs/FAQ.html#react-not -рендеринг. Кроме того, как выглядят рабочие места и ваше начальное состояние?   -  person markerikson    schedule 27.06.2016
comment
Поэтому, если я изменю JobShow, включив в него jobs={jobs}, он будет повторно отображаться по мере обновления заданий, но передача реквизитов, которые ребенку не нужны, кажется неправильным. Похоже, это указывает на то, что состояние не мутирует. Я также могу подтвердить, что сам контейнер повторно отображает и даже повторно запускает функцию renderView, но просто не выполняет повторный рендеринг JobShow.   -  person Dom Hede    schedule 27.06.2016
comment
Просмотр большего количества кода может помочь, но ваши строки jobToUpdate во фрагменте кода редуктора напрямую изменяют этот объект задания, а не делают копию, а только изменяют копию и возвращают эту копию. Так что да, если вы пытаетесь передать этот объект задания дочернему компоненту в качестве реквизита, сама ссылка не изменилась, и дочерний компонент не будет обновляться.   -  person markerikson    schedule 27.06.2016
comment
Я думал, что проблема мутирующего состояния решена с помощью $merge из react-addons-update, а также задания инициализируются как пустой массив [].   -  person Dom Hede    schedule 27.06.2016
comment
Большое спасибо @markerikson. Вы были абсолютно правы!   -  person Dom Hede    schedule 27.06.2016
comment
Скажем так, есть очень веская причина, по которой я добавил этот пункт в FAQ :) Например, я не уверен, что встречал такую ​​проблему, где причина не была< /i> связаны с мутацией. Основное эмпирическое правило: если вы когда-либо использовали = в редьюсере, а левая часть оператора не является чем-то, что вы явно скопировали, вы мутируете. Такие методы, как Array.push(), также являются частыми виновниками.   -  person markerikson    schedule 27.06.2016


Ответы (2)


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

case ActionTypes.EDIT_TODO:
  return Object.assign({}, 
    state, 
    { text: action.text }
  )

Из: https://github.com/mjw56/redux/blob/a8daa288d2beeefefcb88c577a7c0a86b0eb8340/examples/todomvc/reducers/todos.js

В официальных документах Redux также есть больше (redux.js.org/docs/recipes/UsingObjectSpreadOperator.html).

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

person Andy    schedule 27.06.2016

Проблема, как указано в комментариях @markerikson, заключалась в том, что я мутировал состояние. Используя функцию $set из react-addons-update, я успешно обновил содержимое.

Новый фрагмент редуктора выглядит так:

case UPDATE_JOB_STATUS:

  let newStatusdata = action.payload;

  let jobToUpdate = state.all.find(jobbie => jobbie.displayId == newStatusdata.displayId);
  let updatedJob = update(jobToUpdate, {status:{$set: newStatusdata.newStatus}})
  updatedJob = update(updatedJob, {modifiedAt:{$set: newStatusdata.timeModified}})

  let updatedJobIndex = _.indexOf(state.all, state.all.find(jobbie => jobbie.displayId == newStatusdata.displayId));
  let updatedState = update(state.all, {$merge: {[updatedJobIndex]: updatedJob}});

  return { ...state, all: updatedState}
person Dom Hede    schedule 27.06.2016