perf: improve optimistic queries
This commit is contained in:
@@ -3,11 +3,12 @@ import { HiEllipsisHorizontal, HiLink, HiOutlineTrash } from "react-icons/hi2";
|
||||
import Dropdown from "~/components/Dropdown";
|
||||
import { useModal } from "~/providers/modal";
|
||||
|
||||
export default function BoardDropdown() {
|
||||
export default function BoardDropdown({ isLoading }: { isLoading: boolean }) {
|
||||
const { openModal } = useModal();
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
disabled={isLoading}
|
||||
items={[
|
||||
{
|
||||
label: "Edit board URL",
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { api } from "~/utils/api";
|
||||
import { useBoard } from "~/providers/board";
|
||||
import { useModal } from "~/providers/modal";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
export function DeleteBoardConfirmation() {
|
||||
export function DeleteBoardConfirmation({
|
||||
boardPublicId,
|
||||
}: {
|
||||
boardPublicId: string;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const { boardData } = useBoard();
|
||||
const { closeModal } = useModal();
|
||||
|
||||
const deleteBoard = api.board.delete.useMutation({
|
||||
@@ -19,9 +20,9 @@ export function DeleteBoardConfirmation() {
|
||||
});
|
||||
|
||||
const handleDeleteBoard = () => {
|
||||
if (boardData?.publicId)
|
||||
if (boardPublicId)
|
||||
deleteBoard.mutate({
|
||||
boardPublicId: boardData.publicId,
|
||||
boardPublicId: boardPublicId,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -39,7 +40,9 @@ export function DeleteBoardConfirmation() {
|
||||
<Button onClick={() => closeModal()} variant="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleDeleteBoard}>Delete</Button>
|
||||
<Button onClick={handleDeleteBoard} isLoading={deleteBoard.isPending}>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,45 +1,39 @@
|
||||
import Button from "~/components/Button";
|
||||
import { useBoard } from "~/providers/board";
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
interface DeleteListConfirmationProps {
|
||||
listPublicId: string;
|
||||
queryParams: QueryParams;
|
||||
}
|
||||
|
||||
interface QueryParams {
|
||||
boardPublicId: string;
|
||||
members: string[];
|
||||
labels: string[];
|
||||
}
|
||||
|
||||
export function DeleteListConfirmation({
|
||||
listPublicId,
|
||||
queryParams,
|
||||
}: DeleteListConfirmationProps) {
|
||||
const utils = api.useUtils();
|
||||
const { boardData } = useBoard();
|
||||
const { closeModal } = useModal();
|
||||
const { showPopup } = usePopup();
|
||||
|
||||
const refetchBoard = async () => {
|
||||
if (boardData?.publicId) {
|
||||
try {
|
||||
await utils.board.byId.refetch();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const deleteList = api.list.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
closeModal();
|
||||
return refetchBoard();
|
||||
},
|
||||
onError: async () => {
|
||||
closeModal();
|
||||
await refetchBoard();
|
||||
onError: () => {
|
||||
showPopup({
|
||||
header: "Unable to delete list",
|
||||
message: "Please try again later, or contact customer support.",
|
||||
icon: "error",
|
||||
});
|
||||
},
|
||||
onSettled: async () => {
|
||||
closeModal();
|
||||
await utils.board.byId.invalidate(queryParams);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -62,9 +62,11 @@ interface BoardData {
|
||||
const Filters = ({
|
||||
position = "right",
|
||||
boardData,
|
||||
isLoading,
|
||||
}: {
|
||||
position?: "left" | "right";
|
||||
boardData: BoardData | null;
|
||||
isLoading: boolean;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
|
||||
@@ -166,7 +168,11 @@ const Filters = ({
|
||||
menuSpacing="md"
|
||||
position={position}
|
||||
>
|
||||
<Button variant="secondary" iconLeft={<IoFilterOutline />}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={isLoading}
|
||||
iconLeft={<IoFilterOutline />}
|
||||
>
|
||||
Filter
|
||||
</Button>
|
||||
{numOfFilters > 0 && (
|
||||
|
||||
@@ -86,11 +86,11 @@ export function NewCardForm({
|
||||
listId: 2,
|
||||
description: "",
|
||||
labels: oldBoard.labels.filter((label) =>
|
||||
labelPublicIds.includes(label.publicId),
|
||||
args.labelPublicIds.includes(label.publicId),
|
||||
),
|
||||
members:
|
||||
oldBoard.workspace?.members.filter((member) =>
|
||||
memberPublicIds.includes(member.publicId),
|
||||
args.memberPublicIds.includes(member.publicId),
|
||||
) ?? [],
|
||||
_filteredLabels: labelPublicIds.map((id) => ({ publicId: id })),
|
||||
_filteredMembers: memberPublicIds.map((id) => ({ publicId: id })),
|
||||
|
||||
@@ -3,11 +3,11 @@ import { useForm } from "react-hook-form";
|
||||
import { HiXMark } from "react-icons/hi2";
|
||||
|
||||
import type { NewListInput } from "@kan/api/types";
|
||||
import { generateUID } from "@kan/shared/utils";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
import Input from "~/components/Input";
|
||||
import Toggle from "~/components/Toggle";
|
||||
import { useBoard } from "~/providers/board";
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { api } from "~/utils/api";
|
||||
@@ -16,11 +16,24 @@ type NewListFormInput = NewListInput & {
|
||||
isCreateAnotherEnabled: boolean;
|
||||
};
|
||||
|
||||
export function NewListForm({ boardPublicId }: { boardPublicId: string }) {
|
||||
const { refetchBoard, addList } = useBoard();
|
||||
interface QueryParams {
|
||||
boardPublicId: string;
|
||||
members: string[];
|
||||
labels: string[];
|
||||
}
|
||||
|
||||
export function NewListForm({
|
||||
boardPublicId,
|
||||
queryParams,
|
||||
}: {
|
||||
boardPublicId: string;
|
||||
queryParams: QueryParams;
|
||||
}) {
|
||||
const { closeModal } = useModal();
|
||||
const { showPopup } = usePopup();
|
||||
|
||||
const utils = api.useUtils();
|
||||
|
||||
const { register, handleSubmit, reset, setValue, watch } =
|
||||
useForm<NewListFormInput>({
|
||||
defaultValues: {
|
||||
@@ -33,18 +46,41 @@ export function NewListForm({ boardPublicId }: { boardPublicId: string }) {
|
||||
const isCreateAnotherEnabled = watch("isCreateAnotherEnabled");
|
||||
|
||||
const createList = api.list.create.useMutation({
|
||||
onSuccess: async () => {
|
||||
await refetchBoard();
|
||||
onMutate: async (args) => {
|
||||
await utils.board.byId.cancel();
|
||||
|
||||
const currentState = utils.board.byId.getData(queryParams);
|
||||
|
||||
utils.board.byId.setData(queryParams, (oldBoard) => {
|
||||
if (!oldBoard) return oldBoard;
|
||||
|
||||
const newList = {
|
||||
publicId: generateUID(),
|
||||
name: args.name,
|
||||
boardId: 1,
|
||||
boardPublicId,
|
||||
cards: [],
|
||||
index: oldBoard.lists.length,
|
||||
};
|
||||
|
||||
const updatedLists = [...oldBoard.lists, newList];
|
||||
|
||||
return { ...oldBoard, lists: updatedLists };
|
||||
});
|
||||
|
||||
return { previousState: currentState };
|
||||
},
|
||||
onError: async () => {
|
||||
closeModal();
|
||||
await refetchBoard();
|
||||
onError: (_error, _newList, context) => {
|
||||
utils.board.byId.setData(queryParams, context?.previousState);
|
||||
showPopup({
|
||||
header: "Unable to create list",
|
||||
message: "Please try again later, or contact customer support.",
|
||||
icon: "error",
|
||||
});
|
||||
},
|
||||
onSettled: async () => {
|
||||
await utils.board.byId.invalidate(queryParams);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -54,7 +90,6 @@ export function NewListForm({ boardPublicId }: { boardPublicId: string }) {
|
||||
}, []);
|
||||
|
||||
const onSubmit = (data: NewListInput) => {
|
||||
addList(data);
|
||||
const isCreateAnotherEnabled = watch("isCreateAnotherEnabled");
|
||||
if (!isCreateAnotherEnabled) closeModal();
|
||||
reset({
|
||||
|
||||
@@ -6,7 +6,6 @@ import { z } from "zod";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
import Input from "~/components/Input";
|
||||
import { useBoard } from "~/providers/board";
|
||||
import { useModal } from "~/providers/modal";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { api } from "~/utils/api";
|
||||
@@ -25,18 +24,26 @@ const schema = z.object({
|
||||
|
||||
type FormValues = z.infer<typeof schema>;
|
||||
|
||||
interface QueryParams {
|
||||
boardPublicId: string;
|
||||
members: string[];
|
||||
labels: string[];
|
||||
}
|
||||
|
||||
export function UpdateBoardSlugForm({
|
||||
boardPublicId,
|
||||
workspaceSlug,
|
||||
boardSlug,
|
||||
queryParams,
|
||||
}: {
|
||||
boardPublicId: string;
|
||||
workspaceSlug: string;
|
||||
boardSlug: string;
|
||||
queryParams: QueryParams;
|
||||
}) {
|
||||
const { closeModal } = useModal();
|
||||
const { showPopup } = usePopup();
|
||||
const { refetchBoard } = useBoard();
|
||||
const utils = api.useUtils();
|
||||
|
||||
const {
|
||||
register,
|
||||
@@ -51,18 +58,17 @@ export function UpdateBoardSlugForm({
|
||||
});
|
||||
|
||||
const updateBoardSlug = api.board.update.useMutation({
|
||||
onSuccess: async () => {
|
||||
await refetchBoard();
|
||||
closeModal();
|
||||
},
|
||||
onError: () => {
|
||||
closeModal();
|
||||
showPopup({
|
||||
header: "Unable to update board URL",
|
||||
message: "Please try again later, or contact customer support.",
|
||||
icon: "error",
|
||||
});
|
||||
},
|
||||
onSettled: async () => {
|
||||
closeModal();
|
||||
await utils.board.byId.invalidate(queryParams);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -2,19 +2,28 @@ import { useEffect, useState } from "react";
|
||||
import { HiOutlineEye, HiOutlineEyeSlash } from "react-icons/hi2";
|
||||
|
||||
import Button from "~/components/Button";
|
||||
import { useBoard } from "~/providers/board";
|
||||
import { usePopup } from "~/providers/popup";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
interface QueryParams {
|
||||
boardPublicId: string;
|
||||
members: string[];
|
||||
labels: string[];
|
||||
}
|
||||
|
||||
const VisibilityButton = ({
|
||||
visibility,
|
||||
boardPublicId,
|
||||
queryParams,
|
||||
isLoading,
|
||||
}: {
|
||||
visibility: "public" | "private";
|
||||
boardPublicId: string;
|
||||
queryParams: QueryParams;
|
||||
isLoading: boolean;
|
||||
}) => {
|
||||
const { refetchBoard } = useBoard();
|
||||
const { showPopup } = usePopup();
|
||||
const utils = api.useUtils();
|
||||
const [stateVisibility, setStateVisibility] = useState<"public" | "private">(
|
||||
visibility,
|
||||
);
|
||||
@@ -26,8 +35,7 @@ const VisibilityButton = ({
|
||||
const isPublic = stateVisibility === "public";
|
||||
|
||||
const updateBoardVisibility = api.board.update.useMutation({
|
||||
onSuccess: async () => {
|
||||
await refetchBoard();
|
||||
onSuccess: () => {
|
||||
setStateVisibility(isPublic ? "private" : "public");
|
||||
},
|
||||
onError: () => {
|
||||
@@ -37,6 +45,9 @@ const VisibilityButton = ({
|
||||
icon: "error",
|
||||
});
|
||||
},
|
||||
onSettled: async () => {
|
||||
await utils.board.byId.invalidate(queryParams);
|
||||
},
|
||||
});
|
||||
|
||||
const handleUpdateBoardVisibility = () => {
|
||||
@@ -52,6 +63,7 @@ const VisibilityButton = ({
|
||||
onClick={handleUpdateBoardVisibility}
|
||||
iconLeft={isPublic ? <HiOutlineEye /> : <HiOutlineEyeSlash />}
|
||||
isLoading={updateBoardVisibility.isPending}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isPublic ? "Public" : "Private"}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user