Reputation: 10825
All below code was in 1 file at the start of refactoring and worked well. I simplified code a little.
My reducers
folder:
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;
}
}
I have a bunch of errors in Chrome console, like:
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`.
But only for questions
reducer. And If I add console.log
in initialState file, I saw it only 1 time ( I suppose should show 2 times)
Seems questions reducer had not been added to root reducer.
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;
}
Updated:
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);
Upvotes: 1
Views: 85
Reputation: 1297
In the mapStateToProps
,
const mapStateToProps = (state) => {
return state;
};
the state
should be split according to its reducers
. This is because of combineReducers
.
So, when you want to get the actual state when using combineReducers
you will have to do something like this -
const mapStateToProps = (state) => {
return state.address; // or state.question
};
If you want to send all the data of the state (i.e. belonging to both reducers), you can do something like this -
const mapStateToProps = (state) => {
return Object.assign({}, state.address, state.question);
};
or you will have to handle it in the reducer
code.
NOTE: I haven't tried it, you will have to be careful while doing this because since a separate object is being created, it might cause problems with updating.
EDIT: Some thought about the implementation.
PS: I think the reducer
design isn't correct. What I mean is both the address
as well as questions
reducer have the same initial state. So when you do a combineReducer()
, the store.getState()
(i.e. the store state) becomes something like this -
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: '',
}
}
};
rather than this -
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: '',
}
}
I would strongly advice you to move the common state things (like currentAnswer
and currentQuestion
) into a separate reducer.
Edit 2: I just verified it with the following code that Object.assign()
isn't the correct thing to do.
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);
The output is -
{
"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": ""
}
}
Here, the address
has sessionID: 12345
, whereas questions
has sessionID: 1234
, but the result
has sessionID: 1234
.
Thus the Object.assign()
replaces the values set by address
with the values of question
. This is why it seems to work.
The proper way would be to redesign the reducer such that it has common state in a new reducer.
Upvotes: 1