): void => {
+ const { name, value } = e.target;
+ const newState = { ...formState, [name]: value };
+ setFormState(newState);
+
+ if (isEditing) {
+ debouncedSave(newState);
+ }
+ },
+ [formState, debouncedSave, isEditing],
+ );
const handleCancelClick = () => {
setIsEditing(false);
diff --git a/src/components/judging/ModalPopup.tsx b/src/components/judging/ModalPopup.tsx
index ce809c92..f871ce93 100644
--- a/src/components/judging/ModalPopup.tsx
+++ b/src/components/judging/ModalPopup.tsx
@@ -1,5 +1,7 @@
import { generateClient } from "aws-amplify/api";
+import debounce from "lodash.debounce";
import Image from "next/image";
+import { useMemo } from "react";
import { useForm, type SubmitHandler } from "react-hook-form";
import type { Schema } from "@/amplify/data/resource";
import { Button, SelectField } from "@aws-amplify/ui-react";
@@ -83,9 +85,16 @@ const ModalPopup = (props: ModalPopupProps) => {
};
const scoreObject = watch("score") as ScoreObject;
+ const debouncedUpdateScore = useMemo(
+ () =>
+ debounce((id: string, score: string) => {
+ setValue("score", { ...scoreObject, [id]: score });
+ }, 300),
+ [scoreObject, setValue],
+ );
const updateScoringComponent = (id: string, score: string) => {
- setValue("score", { ...scoreObject, [id]: score });
+ debouncedUpdateScore(id, score);
};
return (
diff --git a/src/components/judging/ScoresTable.tsx b/src/components/judging/ScoresTable.tsx
index facde86a..16060b29 100644
--- a/src/components/judging/ScoresTable.tsx
+++ b/src/components/judging/ScoresTable.tsx
@@ -1,4 +1,5 @@
import { generateClient } from "aws-amplify/api";
+import debounce from "lodash.debounce";
import Image from "next/image";
import { useMemo, useState } from "react";
import { useForm } from "react-hook-form";
@@ -163,6 +164,31 @@ export default function JudgingTable(props: JudgingTableProps) {
scoreData?.score ? JSON.parse(scoreData?.score as string) : {}
) as ScoreObject;
+ const debouncedScoreUpdate = useMemo(
+ () =>
+ debounce(
+ async (teamId: string, columnId: string, value: string) => {
+ try {
+ await client.models.Score.update({
+ judgeId: currentUser.username,
+ teamId,
+ score: JSON.stringify({
+ ...scoreObject,
+ [columnId]: value,
+ }),
+ });
+ await refetch();
+ toast.success("Score updated successfully!");
+ } catch (error) {
+ console.error("Error updating score:", error);
+ toast.error("Error updating Score. Try again.");
+ }
+ },
+ 500,
+ ),
+ [client, currentUser.username, scoreObject, refetch],
+ );
+
const scoringComponentIds = useMemo(
() =>
hackathonData.scoringComponents.map(
@@ -246,11 +272,10 @@ export default function JudgingTable(props: JudgingTableProps) {
onChange={async (e) => {
const newValue = e.target.value;
setValue(`score.${columnId}`, newValue);
-
- await handleSave(
- { score: { [columnId]: newValue } },
+ debouncedScoreUpdate(
team.id,
columnId,
+ newValue,
);
}}
>
diff --git a/src/components/reset/ResetPage.tsx b/src/components/reset/ResetPage.tsx
index 6ca07cc4..b21f3e43 100644
--- a/src/components/reset/ResetPage.tsx
+++ b/src/components/reset/ResetPage.tsx
@@ -1,7 +1,9 @@
"use client";
import { generateClient } from "aws-amplify/api";
+import debounce from "lodash.debounce";
import Image from "next/image";
+import { useMemo } from "react";
import { useFieldArray, useForm, type SubmitHandler } from "react-hook-form";
import { toast } from "react-toastify";
import { v4 as uuidv4 } from "uuid";
@@ -111,6 +113,44 @@ export default function ResetPage() {
name: "scoringSidepots",
});
+ const debouncedRemoveComponent = useMemo(
+ () => debounce((index: number) => removeScoringComponent(index), 500),
+ [removeScoringComponent],
+ );
+
+ const debouncedRemoveSidepot = useMemo(
+ () => debounce((index: number) => removeScoringSidepot(index), 500),
+ [removeScoringSidepot],
+ );
+
+ const debouncedAppendComponent = useMemo(
+ () =>
+ debounce(
+ () =>
+ appendScoringComponent({
+ friendlyName: "",
+ isSidepot: false,
+ id: generateId(),
+ }),
+ 500,
+ ),
+ [appendScoringComponent],
+ );
+
+ const debouncedAppendSidepot = useMemo(
+ () =>
+ debounce(
+ () =>
+ appendScoringSidepot({
+ friendlyName: "",
+ isSidepot: true,
+ id: generateId(),
+ }),
+ 500,
+ ),
+ [appendScoringSidepot],
+ );
+
if (hackathonData.isPending || userMutation.isPending)
return (
@@ -151,7 +191,7 @@ export default function ResetPage() {
type="button"
variation="primary"
colorTheme="error"
- onClick={() => removeScoringComponent(index)}
+ onClick={() => debouncedRemoveComponent(index)}
>
-
@@ -161,13 +201,7 @@ export default function ResetPage() {
type="button"
variation="primary"
size="small"
- onClick={() =>
- appendScoringComponent({
- friendlyName: "",
- isSidepot: false,
- id: generateId(),
- })
- }
+ onClick={debouncedAppendComponent}
>
Add Scoring Component
@@ -187,7 +221,7 @@ export default function ResetPage() {
variation="primary"
colorTheme="error"
type="button"
- onClick={() => removeScoringSidepot(index)}
+ onClick={() => debouncedRemoveSidepot(index)}
>
-
@@ -197,13 +231,7 @@ export default function ResetPage() {
type="button"
variation="primary"
size="small"
- onClick={() => {
- appendScoringSidepot({
- friendlyName: "",
- isSidepot: true,
- id: generateId(),
- });
- }}
+ onClick={debouncedAppendSidepot}
>
Add Scoring Sidepot
diff --git a/src/components/teamRegistration/JoinTeamCode.tsx b/src/components/teamRegistration/JoinTeamCode.tsx
index d3ad8246..336282a8 100644
--- a/src/components/teamRegistration/JoinTeamCode.tsx
+++ b/src/components/teamRegistration/JoinTeamCode.tsx
@@ -1,8 +1,9 @@
"use client";
+import debounce from "lodash.debounce";
import Link from "next/link";
import { useRouter } from "next/navigation";
-import { useEffect, useRef, useState, type ChangeEvent } from "react";
+import { useEffect, useMemo, useRef, useState, type ChangeEvent } from "react";
import { toast } from "react-toastify";
import { client } from "@/app/QueryProvider";
import { useMutation } from "@tanstack/react-query";
@@ -11,6 +12,42 @@ import PurpleButton from "../PurpleButton";
export default function JoinTeamCode() {
const [teamIDInput, setTeamIDInput] = useState(Array(4).fill(""));
+ const joinTeamMutation = useMutation({
+ mutationFn: async (teamID: string) => {
+ const toastObj = toast.loading("Joining team...");
+ const res = await client.mutations.AssignUsersToTeams({
+ teamId: teamID,
+ userId: currentUser.username,
+ });
+ if (res.errors) {
+ toast.dismiss(toastObj);
+ throw new Error(res.errors[0].message);
+ }
+ toast.dismiss(toastObj);
+ return res.data;
+ },
+ onSuccess: (data) => {
+ if (data?.statusCode === 200) {
+ toast.success("Team joined successfully");
+ router.push(`/join/team/${teamIDInput.join("")}`);
+ }
+ },
+ onError: (e) => {
+ const error = JSON.parse(e.message);
+ toast.error("Failed to join team " + error.body.value);
+ },
+ mutationKey: ["JoinTeam"],
+ });
+ const debouncedJoinTeam = useMemo(
+ () =>
+ debounce((teamId: string) => {
+ if (teamId.length === 4) {
+ joinTeamMutation.mutate(teamId);
+ }
+ }, 500),
+ [joinTeamMutation.mutate],
+ );
+
useEffect(() => {
const handlePasteEvent = (e: ClipboardEvent) => {
const clipboardContentsText = e.clipboardData?.getData("text");
@@ -18,14 +55,14 @@ export default function JoinTeamCode() {
const teamIDArray = clipboardContentsText.split("");
e.preventDefault();
setTeamIDInput(teamIDArray);
- joinTeamMutation.mutate(teamIDArray.join(""));
+ debouncedJoinTeam(teamIDArray.join(""));
}
};
window.addEventListener("paste", handlePasteEvent);
return () => {
window.removeEventListener("paste", handlePasteEvent);
};
- }, []);
+ }, [debouncedJoinTeam]);
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
const handleTeamIDInput = (
e: ChangeEvent,
@@ -42,35 +79,15 @@ export default function JoinTeamCode() {
if (value && index < teamIDInput.length - 1) {
inputRefs.current[index + 1]?.focus();
}
+
+ if (newTeamIDInput.every((char) => char !== "")) {
+ debouncedJoinTeam(newTeamIDInput.join(""));
+ }
};
+
const router = useRouter();
const { currentUser } = useUser();
- const joinTeamMutation = useMutation({
- mutationFn: async (teamID: string) => {
- const toastObj = toast.loading("Joining team...");
- const res = await client.mutations.AssignUsersToTeams({
- teamId: teamID,
- userId: currentUser.username,
- });
- if (res.errors) {
- toast.dismiss(toastObj);
- throw new Error(res.errors[0].message);
- }
- toast.dismiss(toastObj);
- return res.data;
- },
- onSuccess: (data) => {
- if (data?.statusCode === 200) {
- toast.success("Team joined successfully");
- router.push(`/join/team/${teamIDInput.join("")}`);
- }
- },
- onError: (e) => {
- const error = JSON.parse(e.message);
- toast.error("Failed to join team " + error.body.value);
- },
- mutationKey: ["JoinTeam"],
- });
+
const handleJoinTeam = (e: React.FormEvent) => {
e.preventDefault();
const teamID = teamIDInput.join("");
@@ -80,6 +97,7 @@ export default function JoinTeamCode() {
setTeamIDInput(Array(4).fill(""));
inputRefs.current[0]?.focus();
};
+
return (
<>