catandmouse
catandmouse

Reputation: 11829

Can I use condition in my action reducer?

Basically, in our case, we need to either get an alerts list that shows the first few items (mounting it first time in the DOM) or show the initial list + the next list (clicking a load more button).

Hence we needed to do this condition in our GET_ALERTS action:

case "GET_ALERTS":
    if (action.initialList) {
        newState.list = [...newState.list, action.res.data.list];
    } else {
        newState.list = newState.list.concat(
            action.res.data.list
        );
    }

And when we call the action reducer in our Alerts component, we need to indicate whether initialList is true or false.

E.g.

componentDidMount() {
    this.props.getAlerts(pageNum, true);
}

markAllAsRead() {
   // other code calling api to mark all as read
   this.props.getAlerts(pageNum, false);
}

readMore() {
  // other code that increases pageNum state counter
  this.props.getAlerts(pageNum, true);
}

Anyway in such a case, is it fine to use conditional statement in the reducer?

Upvotes: 3

Views: 6209

Answers (2)

Andrii Muzalevskyi
Andrii Muzalevskyi

Reputation: 3329

I would suggest you to have following set of actions:

  • ALERTS/INIT - loads initial list
  • ALERTS/LOAD_MORE - loads next page and then increments pageNo, so next call will know how many pages are loaded
  • ALERTS/MARK_ALL_AS_READ - does server call and reinitializes list

The store structure

{
    list: [],
    currentPage: 0
}

And component code should not track pageNum

componentDidMount() {
    this.props.initAlerts();
}

markAllAsRead() {
   this.props.markAllAsRead();
}

readMore() {
   this.props.loadMore();
}

Upvotes: 0

boaz_shuster
boaz_shuster

Reputation: 2925

I am against this idea. The reducer has a single responsibility: update Redux state according to the action.

Here are three ways to slove this:

easy way - initialize your list in Redux state to empty list

if you set the list in state to empty list ([]) then it's much simpler. You can basically just change your reducer to this:

case "GET_ALERTS":
    return {...state, list: [...state.list, action.res.data.list]

This will make sure that even if you have get initial list or more items to add to the list, they will be appended. No need to add any logic - which is awesome IMHO.

redux-thunk and separating type into two different types

create two actions: GET_INIT_ALERTS and GET_MORE_ALERTS.

switch(action.type) {
case "GET_INIT_ALERTS":
    return {...state, list: action.res.data.list }
case "GET_MORE_ALERTS":
    return {...state, list: [...state.list, ...action.res.data.list]}
case "CHECK_READ_ALERTS":
    return {...state, read: [...state.read, ...action.res.data.list]}
}

In the component I will have:

componentDidMount() {
    this.props.getInitAlerts();
}

markAllAsRead() {
   // other code calling api to mark all as read
   this.props.getAlerts(pageNum, false);
}

readMore() {
  // other code that increases pageNum state counter
  this.props.getAlerts(pageNum);
}

In alerts action with the help of redux-thunk:

export const getAlerts = (pageNum : number) => (dispatch) => {
    return apiAction(`/alerts/${pageNum}`, 'GET').then(res => dispatch({type: "GET_MORE_ALERTS", res});
}

export const getInitAlerts = () => (dispatch) => {
    return apiAction('/alerts/1', 'GET').then(res => dispatch({type: "GET_INIT_ALERTS", res});
}

I guess you update pageNum after readMore or componentDidMount. Of course you can save that state in Redux and map it back to props and just increment it when calling the getAlerts action.

write your own middleware

Another way to do this is to write an ad-hoc/feature middleware to concat new data to a list.

const concatLists = store => next => action => {
    let newAction = action
    if (action.type.includes("GET") && action.initialList) {
       newAction = {...action, concatList: action.res.data.list}
    } else if (action.type.includes("GET") {
       newAction = {...action, concatList: [...state[action.key].list, action.res.data.list]}
    }
    return next(newAction);
}

And change your reducer to simply push concatList to the state:

case "GET_ALERTS":
    return {...state, list: action.concatList}

In addition, you will have to change your action to include key (in this case the key will be set to alert (or the name of the key where you store the alert state in redux) and initialList to determine whether to concat or not.

BTW, it's a good practice to put these two under the meta key.

{
  type: "GET_ALERT",
  meta: {
    initialList: true,
    key: "alert",
  },
  res: {...}
}

I hope this helps.

Upvotes: 5

Related Questions