diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts new file mode 100644 index 000000000..566d68a46 --- /dev/null +++ b/frontend/src/hooks/index.ts @@ -0,0 +1 @@ +export * from "./useSubmitAction"; diff --git a/frontend/src/hooks/useSubmitAction.ts b/frontend/src/hooks/useSubmitAction.ts new file mode 100644 index 000000000..f6f0fe3c7 --- /dev/null +++ b/frontend/src/hooks/useSubmitAction.ts @@ -0,0 +1,69 @@ +import { SubmitOptions, useSubmit } from "react-router-dom"; + +// From React Router +type JsonObject = { + [Key in string]: JsonValue; +} & { + [Key in string]?: JsonValue | undefined; +}; +type JsonArray = JsonValue[] | readonly JsonValue[]; +type JsonPrimitive = string | number | boolean | null; +type JsonValue = JsonPrimitive | JsonObject | JsonArray; + +/** + * Can be used when using (React Router) actions to explicitly set the type + * of action, and it's payload. This is similar to a Redux action. + * + * @example + * + * const increment: TypedAction = { + * type: "INCREMENT_VALUE", + * payload: 1 + * } + * + * NOTE: Using complex objects in (React Router) `submit()` calls require a method + * to be set and an encType of "application/json" to be used. + * + * @example + * const submit = useSubmit() + * + * submit(increment, { + * method: "POST", // NOTE: Not needed when using `submitAction()`. + * encType: "application/json", // NOTE: Not needed when using `submitAction()`. + * }) + */ +export type TypedAction = { + type: T; + payload: P; +}; + +/** + * A small wrapper around React Routers' `useSubmit()` that takes a `TypedAction` + * as data. The `SubmitOptions` default to method of "POST" and an encType of "application/json". + * + * @example + * const submitAction = useSubmitAction(); + * + * const increment: TypedAction = { + * type: "INCREMENT_VALUE", + * payload: 1 + * } + * + * submitAction(increment); + * + * NOTE: In the (React Router) action handler `request.clone().json()` should be + * used to retrieve the `TypedAction`. If multiple actions should be handled the + * type of the action can be used to determine the applicable logic. + */ +export function useSubmitAction() { + const submit = useSubmit(); + + return (typedAction: TypedAction, options: SubmitOptions = {}) => { + const targetOptions: SubmitOptions = { + method: "POST", + encType: "application/json", + }; + Object.assign(targetOptions, options); + return submit(typedAction, targetOptions); + }; +} diff --git a/frontend/src/pages/destructionlist/detail/Assignees.tsx b/frontend/src/pages/destructionlist/detail/Assignees.tsx index 39c6b6626..e8ba1104d 100644 --- a/frontend/src/pages/destructionlist/detail/Assignees.tsx +++ b/frontend/src/pages/destructionlist/detail/Assignees.tsx @@ -6,8 +6,9 @@ import { SerializedFormData, } from "@maykin-ui/admin-ui"; import React, { FormEvent, useState } from "react"; -import { useActionData, useSubmit } from "react-router-dom"; +import { useActionData } from "react-router-dom"; +import { useSubmitAction } from "../../../hooks"; import { DestructionListAssignee } from "../../../lib/api/destructionLists"; import { formatUser } from "../utils"; import { UpdateDestructionListAction } from "./DestructionListDetail"; @@ -18,7 +19,8 @@ export function AssigneesEditable({ reviewers, }: AssigneesEditableProps) { const errors = useActionData() || {}; - const submit = useSubmit(); + const submitAction = useSubmitAction(); + const reviewerAssignees = [...assignees].splice(1); const [confirmationModalState, setConfirmationModalState] = useState<{ open: boolean; @@ -84,7 +86,7 @@ export function AssigneesEditable({ >; Object.assign(action.payload, { comment: String(comment) }); - submit(action, { method: "PATCH", encType: "application/json" }); + submitAction(action); setConfirmationModalState({ ...confirmationModalState, open: false }); }; diff --git a/frontend/src/pages/destructionlist/detail/DestructionListDetail.tsx b/frontend/src/pages/destructionlist/detail/DestructionListDetail.tsx index b9cf897ed..1fef0e112 100644 --- a/frontend/src/pages/destructionlist/detail/DestructionListDetail.tsx +++ b/frontend/src/pages/destructionlist/detail/DestructionListDetail.tsx @@ -13,11 +13,11 @@ import { import { ActionFunctionArgs } from "@remix-run/router/utils"; import { redirect, useLoaderData } from "react-router-dom"; +import { TypedAction } from "../../../hooks/useSubmitAction"; import { User } from "../../../lib/api/auth"; import { DestructionList, DestructionListItemUpdate, - DestructionListUpdateData, getDestructionList, updateDestructionList, } from "../../../lib/api/destructionLists"; @@ -111,7 +111,7 @@ export function DestructionListDetailPage() { ); } -export type UpdateDestructionListAction = StateMutationAction< +export type UpdateDestructionListAction = TypedAction< "PROCESS_REVIEW" | "UPDATE_ASSIGNEES" | "UPDATE_ZAKEN", T >; diff --git a/frontend/src/pages/destructionlist/detail/DestructionListItems.tsx b/frontend/src/pages/destructionlist/detail/DestructionListItems.tsx index 8c74cf738..1a9ba24df 100644 --- a/frontend/src/pages/destructionlist/detail/DestructionListItems.tsx +++ b/frontend/src/pages/destructionlist/detail/DestructionListItems.tsx @@ -24,10 +24,10 @@ import { useNavigation, useRevalidator, useSearchParams, - useSubmit, } from "react-router-dom"; import { useAsync } from "react-use"; +import { useSubmitAction } from "../../../hooks/useSubmitAction"; import { ReviewItem } from "../../../lib/api/review"; import { ReviewItemResponse, @@ -73,7 +73,8 @@ interface ProcessZaakReviewSelectionDetail { export function DestructionListItems() { const { state } = useNavigation(); const [urlSearchParams, setUrlSearchParams] = useSearchParams(); - const submit = useSubmit(); + const submitAction = useSubmitAction(); + const { storageKey, destructionList, @@ -134,7 +135,7 @@ export function DestructionListItems() { zaakUrls, }, }; - submit(action, { method: "PATCH", encType: "application/json" }); + submitAction(action); }; // Selection actions allowing the user to add/remove zaken to/from the destruction list or escape such flow. @@ -274,7 +275,7 @@ export function DestructionListItems() { }, }; - submit(actionData, { method: "POST", encType: "application/json" }); + submitAction(actionData); }; // Whether the user is processing a review. diff --git a/frontend/src/types/openzaak.ts b/frontend/src/types.d.ts similarity index 100% rename from frontend/src/types/openzaak.ts rename to frontend/src/types.d.ts diff --git a/frontend/src/types/index.d.ts b/frontend/src/types/index.d.ts deleted file mode 100644 index 8f0b41a1f..000000000 --- a/frontend/src/types/index.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./openzaak"; -export * from "./state"; diff --git a/frontend/src/types/state.d.ts b/frontend/src/types/state.d.ts deleted file mode 100644 index feabbd44e..000000000 --- a/frontend/src/types/state.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Should be used when using (React Router) actions to explicitly mark the type - * of action and cary the payload. - * - * This is similar to Redux actions. - * - * @example - * submit({ type: "INCREMENT_VALUE", payload: 1 }, { method: "PATCH", encType: "application/json" }); - */ -type StateMutationAction = { - type: T; - payload: P; -};