Handling errors/failures in ngrx/redux reducer functions - redux

My question relates to redux and more specifically how to handle errors/failures from within reducer functions. I am in reference to the ngrx example app (https://github.com/ngrx/example-app) and the way it handle errors/failures.
Here is the reducer function I am referring to:
export function reducer(state = initialState, action: collection.Actions): State {
switch (action.type) {
case collection.ActionTypes.LOAD: {
return Object.assign({}, state, {
loading: true
});
}
case collection.ActionTypes.LOAD_SUCCESS: {
const books = action.payload;
return {
loaded: true,
loading: false,
ids: books.map(book => book.id)
};
}
case collection.ActionTypes.ADD_BOOK_SUCCESS:
case collection.ActionTypes.REMOVE_BOOK_FAIL: {
const book = action.payload;
if (state.ids.indexOf(book.id) > -1) {
return state;
}
return Object.assign({}, state, {
ids: [ ...state.ids, book.id ]
});
}
case collection.ActionTypes.REMOVE_BOOK_SUCCESS:
case collection.ActionTypes.ADD_BOOK_FAIL: {
const book = action.payload;
return Object.assign({}, state, {
ids: state.ids.filter(id => id !== book.id)
});
}
default: {
return state;
}
}
}
Can someone please explain the necessity for dealing with those two actions from within the reducer function:
REMOVE_BOOK_FAIL
ADD_BOOK_FAIL
For instance why remove the book from the state (in the case of the ADD_BOOK_FAIL action)?
If the add book action has failed, then the book is not present in the store. Is it?

Maybe it's the naming used that makes it a red herring, my guess is that ADD_BOOK_FAIL could be in use somewhere else for a different use case as a fall back mechanism.
I agree the way you describe it this doesnt make sense the developer did it for this reason.

Related

Adding specialized functionality to instance of a reducer factory

In order to decrease code duplication in the redux-related part of my app, I have come up with reducer factories in order to group repetative logic into an abstract reducer which is then turned into concrete reducer instances by passing name param to the factory.
ReduxFactory/reducer.js
const initialState = {
foo: null
}
function reducerFactory(name = '') {
return function reducer(state = initialState, action) {
switch (action.type) {
case `${name}_DO_SOMETHING`: {
return state
}
default:
return state;
}
}
}
export default reducerFactory;
Then I have two modules for which I build instances of a reducer factory:
fileA.js
import reducerFactory from './ReduxFactory/reducer'
const reducer = reducerFactory('OBJECT_A')
fileB.js
import reducerFactory from './ReduxFactory/reducer'
const reducer = reducerFactory('OBJECT_B')
Now imagine that on objects of type B I need to implement some ad-hoc functionality, which I do not want to include into the general reducer factory body because it is too specialized. Is there any valid JS code pattern to implement this ?
If I understand you correctly I think you can do the following:
function reducerFactory(name = '', extras = (x) => x) {
return function reducer(state = initialState, action) {
switch (action.type) {
case `${name}_DO_SOMETHING`: {
return state;
}
default:
return extras(state, action);
}
};
}
const reducer = reducerFactory(
'OBJECT_B',
(state, action) => {
if (action.type === 'extra') {
//return changed state
}
return state;
}
);

Redux Thunk - State Undefined After Dispatching Multiple Actions

When I dispatch an action after dispatching another action, I noticed that my state isFetching becomes undefined. I think it's probably some asynchronous issue with the actions dispatching after the other. How would I fix this so that I can get both actions to dispatch correctly?
My redux module:
const FETCHING = 'FETCHING'
const FETCHING_KEYWORD = 'FETCHING_KEYWORD'
function fetching() {
return {
type: FETCHING,
}
}
function settingKeyWord(keyWord) {
return {
type: FETCHING_KEYWORD,
keyWord,
}
}
export function fetchKeyWord (keyWord) {
return (dispatch, getState) => {
const { isFetching } = getState()
const { keyWord } = getState()
// After I dispatch the two actions here, isFetching becomes undefined
dispatch(fetching())
dispatch(settingKeyWord(keyWord))
}
}
const initialState = {
isFetching: false,
keyWord: '',
}
export default function results(state = initialState, action) {
switch (action.type) {
case FETCHING:
return {
isFetching: true,
}
case FETCHING_KEYWORD:
return {
keyWord: action.keyWord,
}
default:
return state
}
}
The reducer cases need to return the entire state, not just the updated part, so the problem should also occur when dispatching either action normally. You can fix it by using Object.assign or object-spread syntax in the reducer cases. For example, for Fetching:
case FETCHING:
return Object.assign((), state, {
isFetching: true,
})
or
case FETCHING:
return {...state,
isFetching: true,
})

Delete key from immutable object (redux state)

I'm writing a redux reducer to delete a key from a state object:
state = {
items: {
key1: {foo: 'bar'},
key2: {foo: 'baz'}
},
someOtherKey: 'value'
}
My reducer:
function reducer(state, action) {
if (action=='DEL') {
return {
...state,
items: {
...state.items,
[action.key]: undefined
}
}
}
}
I expected that this would return a new state with the respective property deleted, but instead it returns a new state with the key still present in the object but having the value undefined.
Is there an elegant way in ES6 to write such a reducer? I guess I could use Object.assign and delete the property, but the above pattern is so much more expressive, so I'd like to write it this way if possible.
I ended up using the lodash function omit:
import { omit } from 'lodash'
function reducer(state, action) {
if (action=='DEL') {
return {
...state,
items: omit(state.items, action.key)
}
}
}
}
The fact that the lodash library contains such a function leads me to the assumption that there is probably no easier way to express the operation in simple JavaScript. If I'm wrong, let me know; I'd be very interested to hear.
Untested, what about this? It will create a shallow copy (immutable) of each item, except the one you don't care for.
function reducer(state, action) {
if (action == 'DEL') {
return {
...state,
items: Object.keys(state.items).reduce((obj, key) => {
if (key !== action.key) {
obj[key] = { ...state.items[key]
}
}
}, {})
}
}
}
Another technique: copy the object then delete the key:
function reducer(state, action) {
if (action=='DEL') {
let newItems = {...state.items}; // Copy the object
delete newItems[action.key]; // Remove key from the copy
return {
...state,
items: newItems,
};
}
}

Using thunks to funnel into a single action, or use repeated case statements?

Is there any programmatical advantage of one of these approaches over the other?
actions.js
export const USER_CLOSED_DIALOG = 'USER_CLOSED_DIALOG';
export function userClosedDialog() {
return (dispatch) => {
dispatch(closeDialog());
}
}
export USER_SAVED_DATA = 'USER_SAVED_DATA';
export function userSavedData() {
return (dispatch) => {
dispatch(closeDialog());
}
}
export const CLOSE_DIALOG = 'CLOSE_DIALOG';
export function closeDialog() {
return { type: CLOSE_DIALOG }
}
versus:
reducers.js
switch (action.type) {
case USER_CLOSED_DIALOG:
case USER_SAVED_DATA:
case CLOSE_DIALOG:
return { ...state, dialogOpen: false };
default:
return state;
}
Or are these pretty much equivalent and it's just a matter of preference? The only advantage I can see to using thunks is that one could perform additional processing if the user saved data vs just clicking the close button. It at least leaves that option open for later.

Redux action reuse

I'm a beginner in react / redux.
I've finished a basic component <HeatMap /> in my app, with its actions / reducer / store and it works well.
And I'll render another <HeatMap /> with different settings (props).
What I'm trying to do is to separate this 2 component, because when i dispatch an update action in one, the other one performed it simultaneously.
Question 1
I tried this to separate the states in store
import heatMap from './heat-map1'
import {combineReducers} from 'redux';
export let reducers = combineReducers({
heatMap1: heatMap,
heatMap2: heatMap
});
combineReducers and connectthe 2 heatmap in different object in store
export default connect((state)=> {
return {
onState: state.heatMap1.onState,
config: state.heatMap1.config
}
})(CHMSHeatMap1)
and
export default connect((state)=> {
return {
onState: state.heatMap2.onState,
config: state.heatMap2.config
}
})(CHMSHeatMap2)
is this correct?
Question 2
Because 2 component both react when action is dispatched
I'm thinking about separating the shared actions, but I don't think it's a good idea. Or maybe the issue is not here.
So can you tell me what cause this problem and how to solve it?
Here are my reducer
import * as actionTypes from '../actions/heat-map';
import Immutable from 'immutable';
const onState = {
fetching: 'FETCHING',
error: 'ERROR',
drawn: 'DRAWN'
};
const initialState = {
onState: onState.fetching,
config: {}
};
export default function heatMapReducer(state = initialState, action) {
let immutableState = Immutable.fromJS(state);
switch (action.type) {
case actionTypes.INITIALIZING:
return immutableState.set('onState', onState.drawn).set('config', action.payload.initConfig).toJS();
case actionTypes.FETCH_DATA_REQUEST:
return immutableState.set('onState', onState.fetching).toJS();
case actionTypes.FETCH_DATA_SUCCESS:
return immutableState.set('onState', onState.drawn).setIn(['config','series',0,'data'],Immutable.fromJS(action.payload.mapData.data)).toJS();
case actionTypes.FETCH_DATA_FAILURE:
return immutableState.set('onState', onState.error).set('config', action.payload.mapData).toJS();
default:
return state;
}
}
Action is simple
export function initializeConfig(initConfig) {
return {
type: INITIALIZING,
payload: {
text: 'Initializing',
initConfig
}
}
}
export function requireMapData() {
return {
type: FETCH_DATA_REQUEST,
payload: {
text: 'Loading'
}
};
}
..........
//Async Action for fetching map data and redraw the map
export function fetchMapData(address) {
return function (dispatch) {
//dispatch requireMapData action to set the map in loading state
dispatch(requireMapData());
return fetch(address)
.then(fetchUtil.checkHttpStatus) //check if 404
.then(fetchUtil.parseJSON)
.then(mapData => dispatch(fetchDataSucceed(mapData)))
.catch(error => dispatch(fetchDataFailed(error)));
}
}
Thank you my friend.
You cannot duplicate your reducers in the manner you've depicted. Both are going to respond in the exact same way to the exact same actions.
The solution is to have all of your heat map data in the same reducer state. e.g.
const initialState = {
heatMap1: {},
heatMap2: {}
};
export default heatmap(state = initialState, action) {
// etc
Now if you want to use the same actions for both heat maps, you'll need to have an action property specifying which heap map you're targeting. If you have several heat maps, I'd recommend an array of heat maps with each action containing an index or id to target a particular heat map. e.g.
function updateHeatMap(index, value) {
return {
type: UPDATE_HEATMAP,
index: index,
value: value
}
}
You can also take a look at the multireducer module (https://github.com/erikras/multireducer). It was designed to solve exactly the scenario you propose.
So you would be able to configure your store as such:
import multireducer from 'multireducer';
import heatMap from './heat-map1'
import {combineReducers} from 'redux';
export let reducers = combineReducers({
multireducer: multireducer({
heatMap1: heatMap,
heatMap2: heatMap
})
});
After that, you would then need to use connectMultireducer() instead of redux's standard connect() in order to connect the specific slice of the store to particular components like so:
export default connectMultireducer((state)=> {
return {
onState: state.heatMap.onState,
config: state.heatMap.config
}
})(CHMSHeatMap)
And finally in order to get the correct part of the state to each of those components you would pass in the key when rendering them as such:
<CHMSHeatMap multireducerKey="heatMap1"/>
<CHMSHeatMap multireducerKey="heatMap2"/>
Obviously it's better to read the actual docs at the multireducer repo, but that should give a brief overview. Basically the module is just abstracting the process of adding a key-based lookup to each reducer that is created through the multireducer function.
I suggest original concept of multireducer working without any libraries.
The base idea is unique Symbol action types and self-contained Redux-module like this:
import * as services from './../../../api/services';
const initialState = {
list: [],
};
function getListReducer(state, action) {
return {
...state,
list: action.payload.list,
};
}
function removeItemReducer(state, action) {
const { payload } = action;
const list = state.list.filter((item, i) => i !== payload.index);
return {
...state,
list,
};
}
export default class List {
constructor() {
// action types constants
this.GET_LIST = Symbol('GET_LIST');
this.REMOVE_ITEM = Symbol('REMOVE_ITEM');
}
getList = (serviceName) => {
return async (dispatch) => {
const list = await services[serviceName].get();
dispatch({
type: this.GET_LIST,
payload: {
list,
serviceName,
},
});
};
}
removeItem = (index) => {
return (dispatch) => {
dispatch({
type: this.REMOVE_ITEM,
payload: {
index,
},
});
};
}
reducer = (state = initialState, action) => {
switch (action.type) {
case this.GET_LIST:
return getListReducer(state, action);
case this.REMOVE_ITEM:
return removeItemReducer(state, action);
default:
return state;
}
}
}
More information read there.

Resources