Ошибка при разделении редуктора - PullRequest
0 голосов
/ 30 апреля 2018

Весь код ниже был в 1 файле в начале рефакторинга и работал хорошо. Я немного упростил код.

Моя reducers папка:

index.js

import { combineReducers } from 'redux'
import address from './address'
import questions from './questions'

export default combineReducers({
  address,
  questions
});

initialState.js

import { uniqueID } from '../utils/index';

const defaultQuestion = {
  title: 'What is the address of the property?',
  id: 0,
  question_type: 'address'
};

export const initialState = {
  questions: [defaultQuestion],
  sessionID: uniqueID(),
  session: {},
  currentQuestion: defaultQuestion,
  currentAnswer: '',
  addressSelectd: false,
  amount: 0,
  address: {
    address: {},
    isPendind: false,
    isRejected: false,
    isFulfilled: false,
    message: '',
  }
};

address.js

import {
  ON_SELECT_ADDRESS,
  SAVE_ADDRESS_PENDING,
  SAVE_ADDRESS_FULFILLED,
  SAVE_ADDRESS_REJECTED,
} from '../constants/Constants';
import { initialState } from './initialState'
import { nextQuestion } from './questions'

export default function reduce(state = initialState, action) {
  switch (action.type) {
  case ON_SELECT_ADDRESS:
    return {...state,
      currentAnswer: action.payload,
      addressSelectd: true
    };

  case SAVE_ADDRESS_PENDING:
    return {...state,
      address: {
        isPendind: true,
      },
    };

  case SAVE_ADDRESS_FULFILLED:
    return {...state,
      address: {
        isPendind: false,
        isRejected: false,
        isFulfilled: true,
        address: action.payload.address,
      },
      amount: action.payload.amount,
      currentAnswer: '',
      currentQuestion: nextQuestion(state),
    };

  case SAVE_ADDRESS_REJECTED:
    // if (action.payload == 'incorrect_address')
    return {...state,
      currentAnswer: '',
      address: {
        address: {},
        isPendind: false,
        isFulfilled: false,
        isRejected: true,
        message: 'Please find valid address',
      },
    };

  default:
    return state;
  }
}

questions.js

import {
  ON_CHANGE_ANSWER,
  ON_CHANGE_QUESTION,
  GET_QUESTIONS,
  CREATE_SESSION,
  SAVE_ANSWER,
  SAVE_CURRENT_ANSWER,
  ON_FINISH,
} from '../constants/Constants';
import { initialState } from './initialState'
import { isNullOrUndefined } from 'util';

    export const nextQuestion = (state) => {
      let nextId = state.currentQuestion.direct_question_id;
      if (isNullOrUndefined(nextId)) {
        if (state.currentAnswer === 'yes') {
          nextId = state.currentQuestion.yes_question_id;
        } else if (state.currentAnswer === 'no') {
          nextId = state.currentQuestion.no_question_id;
        }
      }
      return state.questions.find((q) => {
        return q.id === nextId;
      });
    }

export default function reduce(state = initialState, action) {
  switch (action.type) {
  case ON_CHANGE_ANSWER:
    return {...state,
      currentAnswer: action.payload
    };

  case ON_CHANGE_QUESTION:
    return {...state,
      currentQuestion: action.payload
    };

  case GET_QUESTIONS:
    return {...state,
      questions: action.payload,
      currentQuestion: action.payload[0]
    };

  case CREATE_SESSION:
    return {...state,
      session: action.payload,
    };

  case SAVE_CURRENT_ANSWER:
    return {...state,
      currentAnswer: action.payload,
    };

  case SAVE_ANSWER:
    return {...state,
      currentAnswer: '',
      currentQuestion: nextQuestion(state),
    };

  case ON_FINISH:
    return initialState;

  default:
    return state;
  }
}

У меня куча ошибок в консоли Chrome, например:

Warning: Failed prop type: Invalid prop `questions` of type `object` supplied to `MyApp`, expected `array`.
Warning: Failed prop type: The prop `currentAnswer` is marked as required in `MyApp`, but its value is `undefined`.

Но только для questions редуктора. И если я добавлю console.log в initialState файл, я видел это только 1 раз (я полагаю, должен показывать 2 раза)

Кажется, что редуктор вопросов не был добавлен к корневому редуктору.

configureStore:

import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducers from '../reducers/index';
import { createLogger } from 'redux-logger';
import DevTools from '../web/containers/DevTools';

const createDevStoreWithMiddleware = compose(
  applyMiddleware(thunk),
  applyMiddleware(createLogger()),
  DevTools.instrument()
)(createStore);

export default function configureStore() {
  const store = createDevStoreWithMiddleware(reducers);

  return store;
}

Обновлен:

App.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import AddressSearch from '../components/AddressSearch';
import FinalScreen from '../components/FinalScreen';
import {
  onChangeAnswer,
  onChangeQuestion,
  getQuestions,
  saveAnswer,
  createSession,
  onFinish
} from '../../actions/actions';

class MyApp extends Component {

  static propTypes = {
    questions: PropTypes.array.isRequired,
    sessionID: PropTypes.string.isRequired,
    session: PropTypes.object.isRequired,
    currentQuestion: PropTypes.object.isRequired,
    currentAnswer: PropTypes.string.isRequired,
    address: PropTypes.object.isRequired,
    amount: PropTypes.number,
  };

  componentDidMount() {
    this.props.actions.getQuestions();
    this.props.actions.createSession();
  }

  onReset() {
    this.props.actions.onFinish();
    this.componentDidMount();
  }

  nextQuestion(text) {
    if (text.length > 0) {
      this.props.actions.saveAnswer(text);
    }
  }

  renderAnswers() {
    const props = this.props;
    if (props.currentQuestion.question_type === 'address') {
      return <AddressSearch
        currentAnswer={props.currentAnswer}
        message={props.address.message}
        />;
    } else if (props.currentQuestion.question_type === 'text') {
      return [
        <input
          className="question-input"
          value={props.currentAnswer}
          onChange={(event) => props.actions.onChangeAnswer(event.target.value)}
        />,
        <button
          className="main-button"
          onClick={() => this.nextQuestion(props.currentAnswer)}>
            NEXT
        </button>
      ];
    } else if (props.currentQuestion.question_type === 'bool') {
      return [
        <button
          className="yes-no-button"
          onClick={() => this.nextQuestion('yes')}>
            YES
        </button>,
        <button
          className="yes-no-button"
          onClick={() => this.nextQuestion('no')}>
            NO
        </button>
      ];
    } else if (props.currentQuestion.question_type === 'screen') {
      return (
        <button
          className="main-button"
          onClick={() => this.onReset()}>
            Back
        </button>
      );
    }
  }

  containerInner() {
    if (this.props.currentQuestion.question_type === 'success') {
      return <FinalScreen amount={this.props.amount} />;
    } else {
      return [
        <div key={0} className="question">
          {this.props.currentQuestion.title}
        </div>,
        <div key={1} className="answer">
          {this.renderAnswers()}
        </div>
      ];
    }
  }

  render() {
    return (
      <div className="react-native-web">
        {this.containerInner()}
      </div>
      );
    }
  }

const mapStateToProps = (state) => {
  return state;
};

const mapDispatchToProps = (dispatch) => {
  return {
    actions: {
      getQuestions: () => dispatch(getQuestions()),
      createSession: () => dispatch(createSession()),
      saveAnswer: (text) => dispatch(saveAnswer(text)),
      onChangeAnswer: (text) => dispatch(onChangeAnswer(text)),
      onChangeQuestion: (obj) => dispatch(onChangeQuestion(obj)),
      onFinish: () => dispatch(onFinish()),
    }
  };
};

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

1 Ответ

0 голосов
/ 30 апреля 2018

В mapStateToProps,

const mapStateToProps = (state) => {
  return state;
};

state следует разделить в соответствии с reducers. Это из-за combineReducers.

Итак, когда вы хотите получить фактическое состояние при использовании combineReducers, вам придется сделать что-то вроде этого -

const mapStateToProps = (state) => {
  return state.address; // or state.question
};

Если вы хотите отправить все данные о состоянии (т. Е. Принадлежащие обоим редукторам), вы можете сделать что-то вроде этого -

const mapStateToProps = (state) => {
  return Object.assign({}, state.address, state.question);
};

или вам придется обработать его в коде reducer.

ПРИМЕЧАНИЕ: Я не пробовал, вам нужно быть осторожным при этом, потому что, поскольку создается отдельный объект, это может вызвать проблемы с обновлением.


РЕДАКТИРОВАТЬ: Некоторые думали о реализации.

PS: Я думаю, что reducer дизайн не правильный. Я имею в виду, что оба редуктора address и questions имеют одинаковое начальное состояние. Поэтому, когда вы делаете combineReducer(), store.getState() (т.е. состояние магазина) становится примерно таким -

state = {
  address: {
    questions: [{
      title: 'What is the address of the property?',
      id: 0,
      question_type: 'address'
    }],
    sessionID: 1234,
    session: {},
    currentQuestion: defaultQuestion,
    currentAnswer: '',
    addressSelectd: false,
    amount: 0,
    address: {
      address: {},
      isPendind: false,
      isRejected: false,
      isFulfilled: false,
      message: '',
    }
  },
  questions: {
    questions: [{
      title: 'What is the address of the property?',
      id: 0,
      question_type: 'address'
    }],
    sessionID: 1234,
    session: {},
    currentQuestion: defaultQuestion,
    currentAnswer: '',
    addressSelectd: false,
    amount: 0,
    address: {
      address: {},
      isPendind: false,
      isRejected: false,
      isFulfilled: false,
      message: '',
    }
  }
};

а не это -

state = {
  questions: [{
    title: 'What is the address of the property?',
    id: 0,
    question_type: 'address'
  }],
  sessionID: 1234,
  session: {},
  currentQuestion: defaultQuestion,
  currentAnswer: '',
  addressSelectd: false,
  amount: 0,
  address: {
    address: {},
    isPendind: false,
    isRejected: false,
    isFulfilled: false,
    message: '',
  }
}

Я бы настоятельно рекомендовал вам перенести вещи общего состояния (например, currentAnswer и currentQuestion) в отдельный редуктор.


Редактировать 2: Я только что проверил это с помощью следующего кода, что Object.assign() не является правильным решением.

var address = {
  questions: [{
    title: 'What is the address of the property?',
    id: 0,
    question_type: 'address'
  }],
  sessionID: 12345,
  session: {},
  currentQuestion: defaultQuestion,
  currentAnswer: '',
  addressSelectd: false,
  amount: 0,
  address: {
    address: {},
    isPendind: false,
    isRejected: false,
    isFulfilled: false,
    message: ''
  }
};

var questions = {
  questions: [{
    title: 'What is the address of the property?',
    id: 0,
    question_type: 'address'
  }],
  sessionID: 1234,
  session: {},
  currentQuestion: defaultQuestion,
  currentAnswer: '',
  addressSelectd: false,
  amount: 0,
  address: {
    address: {},
    isPendind: false,
    isRejected: false,
    isFulfilled: false,
    message: ''
  }
};

var result = Object.assign({}, address, questions);
console.log(result);

Выход -

{
  "questions": [
    {
      "title": "What is the address of the property?",
      "id": 0,
      "question_type": "address"
    }
  ],
  "sessionID": 1234,
  "session": {},
  "currentQuestion": {
    "title": "What is the address of the property?",
    "id": 0,
    "question_type": "address"
  },
  "currentAnswer": "",
  "addressSelectd": false,
  "amount": 0,
  "address": {
    "address": {},
    "isPendind": false,
    "isRejected": false,
    "isFulfilled": false,
    "message": ""
  }
}

Здесь address имеет sessionID: 12345, тогда как questions имеет sessionID: 1234, но result имеет sessionID: 1234.

Таким образом, Object.assign() заменяет значения, установленные address, значениями question. Вот почему это похоже на работу.

Правильным способом было бы перепроектировать редуктор так, чтобы он имел общее состояние в новом редукторе.

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