I'm using useReducer
on ReactJS and I've defined a couple of action interfaces to use them in my reducer function
. I see that I use the same properties, but sometimes different payload values.
interface MembersAction {
type: "JOIN" | "LEAVE";
payload: string;
}
interface MessagesAction {
type: "MESSAGE";
payload: Message;
}
interface ErrorAction {
type: "ERROR";
payload: string;
}
interface RoomAction {
type: "ROOM";
payload: RoomData;
}
To be honest it feels like bloat and I feel like there must be a better way to achieve what I'm doing.
Here's the reducer function:
type Action = MembersAction | MessagesAction | RoomAction | ErrorAction;
const reducer = (state: RoomState, action: Action) => {
switch (action.type) {
case "JOIN":
return {
...state,
members: [...state.members, action.payload],
};
case "LEAVE":
return {
...state,
members: state.members.splice(state.members.indexOf(action.payload), 1),
};
case "MESSAGE":
return {
...state,
messages: [...state.messages, action.payload],
};
case "ROOM":
return action.payload;
case "ERROR":
return state.error
? {
...state,
}
: {
...state,
error: action.payload,
};
default:
return state;
}
};
Your code looks fine to me, and seems to behave well in this playground link.
payload
type correctly in the switch
block.payload
type is not compatible between your different Actions, so there isn't much to be gained here by treating payload
polymorphically.reducer
does, but that accepts some class definition boilerplate instead of your switch
statement boilerplate.When thinking through this, decide whether it is more likely that you'll be adding Actions that need to express a new behavior (such that the behavior should be encapsulated in an Action), or adding self-contained functions that operate on most or all new actions (such that the behavior should be encapsulated in a switch
like you have). If it's unclear, leave it—personally I'd gladly accept code as clearly-defined as you have it.