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 ponent, we need to indicate whether initialList
is true or false.
E.g.
ponentDidMount() {
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?
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 ponent, we need to indicate whether initialList
is true or false.
E.g.
ponentDidMount() {
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?
Share Improve this question asked Oct 30, 2018 at 4:39 catandmousecatandmouse 11.8k24 gold badges95 silver badges158 bronze badges 2- You could consider using getState() in your action creator to conditionally create the appropriate payload before dispatching to the reducer to avoid conditional logic in your reducer. – Alexander Staroselsky Commented Oct 30, 2018 at 4:49
- This might help stackoverflow./questions/45874393/conditions-in-reducers – Praveen Commented Oct 30, 2018 at 4:52
2 Answers
Reset to default 5I 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 ponent I will have:
ponentDidMount() {
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 ponentDidMount
. 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.
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 ponent code should not track pageNum
ponentDidMount() {
this.props.initAlerts();
}
markAllAsRead() {
this.props.markAllAsRead();
}
readMore() {
this.props.loadMore();
}