Skip to content

Commit

Permalink
Add more helpful error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon committed Jul 8, 2015
1 parent 97d90d0 commit de685de
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 14 deletions.
23 changes: 21 additions & 2 deletions src/utils/composeReducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,34 @@ import mapValues from '../utils/mapValues';
import pick from '../utils/pick';
import invariant from 'invariant';

function getErrorMessage(key, action) {
const actionType = action && action.type;
const actionName = actionType && `"${actionType}"` || 'an action';
const reducerName = `Reducer "${key}"`;

if (actionType === '@@INIT') {
return (
`${reducerName} returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, ` +
`you must explicitly return the initial state.`
);
}

return (
`Reducer "${key}" returned undefined handling ${actionName}. ` +
`To ignore an action, you must explicitly return the previous state.`
);
}

export default function composeReducers(reducers) {
const finalReducers = pick(reducers, (val) => typeof val === 'function');

return function composition(state = {}, action) {
return mapValues(finalReducers, (reducer, key) => {
const newState = reducer(state[key], action);
invariant(
typeof newState !== 'undefined',
`Reducer ${key} returns undefined. By default reducer should return original state.`
typeof newState !== 'undefined',
getErrorMessage(key, action)
);
return newState;
});
Expand Down
54 changes: 42 additions & 12 deletions test/composeReducers.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,59 @@ describe('Utils', () => {
).toEqual(['stack']);
});

it('should throw an error if undefined return from reducer', () => {
it('should throw an error if a reducer returns undefined', () => {
const reducer = composeReducers({
stack: (state = []) => state,
bad: (state = [], action) => {
if (action.type === 'something') {
undefinedByDefault(state = 0, action) {
switch (action && action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
case '@@INIT':
return state;
default:
return undefined;
}
}
});
expect(() => reducer({}, {type: '@@testType'})).toThrow();

const initialState = reducer(undefined, { type: '@@INIT' });
expect(
() => reducer(initialState, { type: 'whatever' })
).toThrow(
/"undefinedByDefault".*"whatever"/
);
expect(
() => reducer(initialState, null)
).toThrow(
/"undefinedByDefault".*an action/
);
expect(
() => reducer(initialState, {})
).toThrow(
/"undefinedByDefault".*an action/
);
});
it('should throw an error if undefined return not by default', () => {

it('should throw an error if a reducer returns undefined initializing', () => {
const reducer = composeReducers({
stack: (state = []) => state,
bad: (state = 1, action) => {
if (action.type !== 'something') {
undefinedInitially(state, action) {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
return state;
}
}
});
expect(reducer({}, {type: '@@testType'})).toEqual({stack: [], bad: 1});
expect(() => reducer({}, {type: 'something'})).toThrow();

expect(
() => reducer(undefined, { type: '@@INIT' })
).toThrow(
/"undefinedInitially".*initialization/
);
});
});
});

0 comments on commit de685de

Please sign in to comment.