From b16aaaca005e94c4fd5ac586e5f99891d7d1792c Mon Sep 17 00:00:00 2001 From: Jeffrey Carl Faden Date: Tue, 20 Jun 2023 11:22:33 -0700 Subject: [PATCH] Silence Redux warnings by not storing functions in the state --- src/actions/index.ts | 22 +++++++++++++ src/actions/modals.ts | 6 +++- src/actions/restaurants.ts | 2 +- src/actions/users.ts | 4 +-- .../ConfirmModal/ConfirmModalContainer.ts | 26 +++++++++------ .../PastDecisionsModalContainer.ts | 9 ++++-- .../RestaurantDropdownContainer.ts | 3 +- .../TagManagerItem/TagManagerItemContainer.ts | 8 ++--- src/interfaces.ts | 32 +++++++++++-------- src/routes/main/teams/Teams.tsx | 15 +++++---- src/routes/main/teams/TeamsContainer.ts | 17 +++------- src/routes/team/team/Team.tsx | 14 ++++---- src/routes/team/team/TeamContainer.ts | 3 +- 13 files changed, 100 insertions(+), 61 deletions(-) create mode 100644 src/actions/index.ts diff --git a/src/actions/index.ts b/src/actions/index.ts new file mode 100644 index 000000000..2c67d5deb --- /dev/null +++ b/src/actions/index.ts @@ -0,0 +1,22 @@ +import { ThunkAction } from "@reduxjs/toolkit"; +import { Action, State } from "../interfaces"; +import { removeRestaurant } from "./restaurants"; +import { removeTag } from "./tags"; +import { changeUserRole, removeUser } from "./users"; + +const generateConfirmableActions = < + T extends { + [K in keyof T]: ( + ...args: Parameters + ) => ThunkAction, State, unknown, Action>; + } +>( + actions: T +) => actions; + +export const confirmableActions = generateConfirmableActions({ + changeUserRole, + removeRestaurant, + removeTag, + removeUser, +}); diff --git a/src/actions/modals.ts b/src/actions/modals.ts index 8ac4675c7..0346c7399 100644 --- a/src/actions/modals.ts +++ b/src/actions/modals.ts @@ -1,3 +1,4 @@ +import { confirmableActions } from "."; import { Action, ConfirmOpts, PastDecisionsOpts } from "../interfaces"; export function showModal(name: string): Action; @@ -5,7 +6,10 @@ export function showModal( name: "pastDecisions", opts?: PastDecisionsOpts ): Action; -export function showModal(name: "confirm", opts?: ConfirmOpts): Action; +export function showModal( + name: "confirm", + opts?: ConfirmOpts +): Action; export function showModal(name: unknown, opts?: unknown): unknown { return { diff --git a/src/actions/restaurants.ts b/src/actions/restaurants.ts index d5eab3d42..4abef9db5 100644 --- a/src/actions/restaurants.ts +++ b/src/actions/restaurants.ts @@ -274,7 +274,7 @@ export function addRestaurant( export function removeRestaurant( id: number -): ThunkAction, State, unknown, Action> { +): ThunkAction, State, unknown, Action> { return (dispatch) => { dispatch(deleteRestaurant(id)); return fetch(`/api/restaurants/${id}`, { diff --git a/src/actions/users.ts b/src/actions/users.ts index aa3c56f88..fecc4c14d 100644 --- a/src/actions/users.ts +++ b/src/actions/users.ts @@ -93,7 +93,7 @@ export function userDeleted(id: number, team: Team, isSelf: boolean): Action { export function removeUser( id: number, team: Team -): ThunkAction { +): ThunkAction, State, unknown, Action> { return (dispatch, getState) => { const state = getState(); let isSelf = false; @@ -182,7 +182,7 @@ export function userPatched( export function changeUserRole( id: number, type: RoleType -): ThunkAction { +): ThunkAction, State, unknown, Action> { const payload = { id, type }; return (dispatch, getState) => { const state = getState(); diff --git a/src/components/ConfirmModal/ConfirmModalContainer.ts b/src/components/ConfirmModal/ConfirmModalContainer.ts index b548c9f7f..1a9abffdc 100644 --- a/src/components/ConfirmModal/ConfirmModalContainer.ts +++ b/src/components/ConfirmModal/ConfirmModalContainer.ts @@ -1,30 +1,36 @@ import { connect } from "react-redux"; +import { confirmableActions } from "../../actions"; import { hideModal } from "../../actions/modals"; import ConfirmModal from "./ConfirmModal"; -import { Dispatch, State } from "../../interfaces"; +import { + ConfirmModal as ConfirmModalType, + Dispatch, + State, +} from "../../interfaces"; const modalName = "confirm"; -const mapStateToProps = (state: State) => ({ - actionLabel: state.modals[modalName].actionLabel!, - body: state.modals[modalName].body, - action: state.modals[modalName].action, - shown: !!state.modals[modalName].shown, -}); +const mapStateToProps = ( + state: State +) => state.modals[modalName] as ConfirmModalType; const mapDispatchToProps = (dispatch: Dispatch) => ({ dispatch, hideModal: () => dispatch(hideModal("confirm")), }); -const mergeProps = ( - stateProps: ReturnType, +const mergeProps = ( + stateProps: ConfirmModalType, dispatchProps: ReturnType ) => ({ ...stateProps, ...dispatchProps, handleSubmit: () => { - dispatchProps.dispatch(stateProps.action!); + dispatchProps.dispatch( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + confirmableActions[stateProps.action](...stateProps.actionArgs) + ); dispatchProps.hideModal(); }, }); diff --git a/src/components/PastDecisionsModal/PastDecisionsModalContainer.ts b/src/components/PastDecisionsModal/PastDecisionsModalContainer.ts index 5a6997f45..b9c3a9e52 100644 --- a/src/components/PastDecisionsModal/PastDecisionsModalContainer.ts +++ b/src/components/PastDecisionsModal/PastDecisionsModalContainer.ts @@ -1,7 +1,11 @@ import { connect } from "react-redux"; import { decide } from "../../actions/decisions"; import { hideModal } from "../../actions/modals"; -import { Dispatch, State } from "../../interfaces"; +import { + Dispatch, + State, + PastDecisionsModal as PastDecisionsModalType, +} from "../../interfaces"; import { getDecisionsByDay } from "../../selectors/decisions"; import { getRestaurantEntities } from "../../selectors/restaurants"; import PastDecisionsModal from "./PastDecisionsModal"; @@ -10,7 +14,8 @@ const modalName = "pastDecisions"; const mapStateToProps = (state: State) => ({ decisionsByDay: getDecisionsByDay(state), - restaurantId: state.modals[modalName].restaurantId, + restaurantId: (state.modals[modalName] as PastDecisionsModalType) + .restaurantId, restaurantEntities: getRestaurantEntities(state), shown: !!state.modals[modalName].shown, }); diff --git a/src/components/RestaurantDropdown/RestaurantDropdownContainer.ts b/src/components/RestaurantDropdown/RestaurantDropdownContainer.ts index daea85225..f72f82066 100644 --- a/src/components/RestaurantDropdown/RestaurantDropdownContainer.ts +++ b/src/components/RestaurantDropdown/RestaurantDropdownContainer.ts @@ -56,7 +56,8 @@ const mergeProps = ( showModal("confirm", { actionLabel: "Delete", body: `Are you sure you want to delete ${stateProps.restaurant.name}?`, - action: removeRestaurant(ownProps.id), + action: "removeRestaurant", + actionArgs: [ownProps.id], }) ), showEditNameForm: () => { diff --git a/src/components/TagManagerItem/TagManagerItemContainer.ts b/src/components/TagManagerItem/TagManagerItemContainer.ts index 578a32059..885bbd492 100644 --- a/src/components/TagManagerItem/TagManagerItemContainer.ts +++ b/src/components/TagManagerItem/TagManagerItemContainer.ts @@ -1,8 +1,7 @@ import { connect } from "react-redux"; import { getTagById } from "../../selectors/tags"; import { showModal } from "../../actions/modals"; -import { removeTag } from "../../actions/tags"; -import { Dispatch, State } from "../../interfaces"; +import { ConfirmOpts, Dispatch, State } from "../../interfaces"; import TagManagerItem from "./TagManagerItem"; interface OwnProps { @@ -28,11 +27,12 @@ const mergeProps = ( handleDeleteClicked() { dispatchProps.dispatch( showModal("confirm", { + action: "removeTag", + actionArgs: [ownProps.id], actionLabel: "Delete", body: `Are you sure you want to delete the “${stateProps.tag.name}” tag? All restaurants will be untagged.`, - action: removeTag(ownProps.id), - }) + } as ConfirmOpts<"removeTag">) ); }, }); diff --git a/src/interfaces.ts b/src/interfaces.ts index 93b4a055d..0e54a6852 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,10 +1,11 @@ import { Application, RequestHandler } from "express"; -import { EnhancedStore, ThunkAction, ThunkDispatch } from "@reduxjs/toolkit"; +import { EnhancedStore, ThunkDispatch } from "@reduxjs/toolkit"; import { BrowserHistory } from "history"; import { InsertCSS } from "isomorphic-style-loader/StyleContext"; import { ReactNode } from "react"; import { ResolveContext } from "universal-router"; import { WebSocket } from "ws"; +import { confirmableActions } from "./actions"; import { Decision as DecisionModel, Restaurant as RestaurantModel, @@ -387,7 +388,7 @@ export type Action = | { type: "SHOW_MODAL"; name: "confirm"; - opts: ConfirmOpts; + opts: ConfirmOpts; } | { type: "SHOW_MODAL"; @@ -451,12 +452,25 @@ export interface Notification { ); } -export type ConfirmOpts = { +export type ConfirmOpts = { actionLabel: string; body: string; - action: Action | ThunkAction; + action: T; + actionArgs: Parameters<(typeof confirmableActions)[T]>; }; +export type BaseModal = { + shown: boolean; +}; + +export type ConfirmModal = + BaseModal & ConfirmOpts; +export type PastDecisionsModal = BaseModal & PastDecisionsOpts; +export type Modal = + | BaseModal + | ConfirmModal + | PastDecisionsModal; + export interface ListUiItem { isEditingName?: boolean; editNameFormValue?: string; @@ -489,15 +503,7 @@ interface BaseState { host: string; notifications: Notification[]; modals: { - [index: string]: { - action?: - | Action - | ThunkAction | void, State, unknown, Action>; - actionLabel?: string; - body?: ReactNode; - restaurantId?: number; - shown: boolean; - }; + [index: string]: Modal; }; listUi: { [index: number]: ListUiItem; diff --git a/src/routes/main/teams/Teams.tsx b/src/routes/main/teams/Teams.tsx index fd7d03e89..9efa8901e 100644 --- a/src/routes/main/teams/Teams.tsx +++ b/src/routes/main/teams/Teams.tsx @@ -5,29 +5,30 @@ import ListGroup from "react-bootstrap/ListGroup"; import { FaTimes } from "react-icons/fa"; import Container from "react-bootstrap/Container"; import Link from "../../../components/Link/Link"; -import { ConfirmOpts, Team } from "../../../interfaces"; +import { ConfirmOpts, Team, User } from "../../../interfaces"; import s from "./Teams.scss"; interface TeamsProps { - confirm: (props: ConfirmOpts) => void; + confirm: (props: ConfirmOpts<"removeUser">) => void; host: string; - leaveTeam: (team: Team) => () => void; teams: Team[]; + user: User; } class Teams extends Component { - confirmLeave = (team: Team) => (event: MouseEvent) => { + confirmLeave = (user: User, team: Team) => (event: MouseEvent) => { event.preventDefault(); this.props.confirm({ actionLabel: "Leave", body: `Are you sure you want to leave this team? You will need to be invited back by another member.`, - action: this.props.leaveTeam(team), + action: "removeUser", + actionArgs: [user.id, team], }); }; render() { - const { host, teams } = this.props; + const { host, teams, user } = this.props; return (
@@ -46,7 +47,7 @@ You will need to be invited back by another member.`,
{team.name}