chatmosphere-app
chatmosphere-app copied to clipboard
Evaluate use of one Store with reducers -> testable
One Option as suggested by zustand staff out of their experience was using one store in the end (makes picking way easier)
I think with using reducers this could work since the store object is not bloated with all method details; also the methods itself would be pure and thus testable
Maybe like this:
ActionMap Type Definition
/**
* Create an action map for reducers.
* We check which action is used and dynamically generate the types for the payload
* Courtesy of: https://medium.com/hackernoon/finally-the-typescript-redux-hooks-events-blog-you-were-looking-for-c4663d823b01
*/
type ActionMap<M extends { [index: string]: any }> = {
[Key in keyof M]: M[Key] extends undefined
? {
type: Key;
}
: {
type: Key;
payload: M[Key];
};
};
export default ActionMap;
GlobalActions
type GlobalActions = ActionMap<ActionPayload>[keyof ActionMap<ActionPayload>];
Actions as enums
enum Action {
addUser = "ADD_USER",
}
ActionPayload Mapping
type ActionPayload = {
[Action.addUser]: AddUserParam;
}
Params
type AddUserParam = {
id: string;
name: string;
};
Initial State
const initialState: ConferenceState = {
users: [],
// yes, localUser in here would work nicely
};
Reducer
const reducer = (state: ConferenceState, action: GlobalActions): ConferenceState => {
switch (action.type) {
case Action.addUser:
return addUser(state, action.payload);
// ...
default:
return state;
}
};
Methods (can live inside their own files)
function addUser(
state: ConferenceState,
{ id, name }: AddUserParam
): ConferenceState {
let newUsers: User[];
newUsers = updateArray(state.users, userIndex, {
...state.users[userIndex],
name: name,
});
return {
...state,
users: newUsers,
};
}
Context
const ConferenceContext = createContext<{
conference: ConferenceState;
dispatch: React.Dispatch<GlobalActions>;
}>({ conference: initialState, dispatch: () => [] });
export const ConferenceStoreProvider = ({
children,
}: {
children: JSX.Element;
}) => {
const [store, dispatch] = useReducer(reducer, initialState);
return (
<ConferenceContext.Provider value={{ conference: store, dispatch }}>
{children}
</ConferenceContext.Provider>
);
};
Actual store
export const useConference = () => useContext(ConferenceContext);
export const useConferenceStore = () => {
const { conference, dispatch } = useConference();
return {
users: conference.users,
addUser: (id: string, name: string) =>
dispatch({ type: Action.addUser, payload: { id, name } }),
// ex: localUser, .... etc.
};
};
Utility (updateArray)
function updateArray<T>(array: T[], index: number, value: T): T[] {
const newArray = [...array];
newArray.splice(index, 1, value);
return newArray;
}