提问人:Spoeky 提问时间:11/18/2023 更新时间:11/18/2023 访问量:64
NextJS 乐观 UI 更新与 useOptimistic 不起作用
NextJS optimistic UI updates with useOptimistic not working
问:
我正在尝试为我的 Web 应用程序创建一个 PostItem 组件。我使用 NextJs 14、TypeScript、TailwindCSS 和 Prisma。 3 天多来,我一直在尝试在用户喜欢或保存帖子时实现乐观的 UI 更新。 下面的代码有一个奇怪的问题,当我喜欢一个帖子时,它会立即在 UI 上更新,点赞数增加/减少,但一旦发出服务器请求,它就会重置为以前的状态,即使后端没有错误。
PostItem.tsx:
"use client";
import { Bookmark, Heart, MessageCircle } from "lucide-react";
import { UserAvatar } from "./UserAvatar";
import Link from "next/link";
import Image from "next/image";
import { AspectRatio } from "../shadcn/ui/aspect-ratio";
import { Skeleton } from "../shadcn/ui/skeleton";
import { formatDateToString } from "@/lib/utils";
import {
deletePost,
patchPostLike,
patchPostSave,
} from "@/lib/actions/post.actions";
import { useState, useOptimistic } from "react";
import LoginModal from "./LoginModal";
interface PostItemProps {
image: string;
likes: string[];
saves: string[];
comments: number;
id: string;
author: { name: string; image: string; id: string };
userId?: string;
createdAt: Date;
}
function PostItem({
image,
likes,
saves,
comments,
id,
userId,
author,
createdAt,
}: PostItemProps) {
const [serverLikes, setServerLikes] = useState(likes);
const [serverSaves, setServerSaves] = useState(saves);
const [serverLiked, setServerLiked] = useState(
serverLikes.includes(userId || "")
);
const [serverSaved, setServerSaved] = useState(
serverSaves.includes(userId || "")
);
const [optimisticLikes, setOptimisticLikes] = useOptimistic(serverLikes);
const [optimisticLiked, setOptimisticLiked] = useOptimistic(serverLiked);
const [optimisticSaved, setOptimisticSaved] = useOptimistic(serverSaved);
const userURL = `/user/${author.name}`;
const postURL = `/post/${id}`;
const formattedDate = formatDateToString(createdAt);
const handleLikeClick = async () => {
if (!userId) return;
try {
const updatedLikes = serverLiked
? serverLikes.filter((like: string) => like !== userId)
: [...serverLikes, userId];
setOptimisticLikes(updatedLikes);
setOptimisticLiked(updatedLikes.includes(userId));
const res = await patchPostLike(id, userId, serverLikes);
if (res.error != undefined) {
return;
}
setServerLikes(res.success);
setServerLiked(res.success.includes(userId));
} catch (error: any) {
console.error("Failed to update like:", error.message);
}
};
const handleSaveClick = async () => {
if (!userId) return;
try {
const updatedSaves = serverSaved
? serverSaves.filter((save: string) => save !== userId)
: [...serverSaves, userId];
setOptimisticSaved(updatedSaves.includes(userId));
const res = await patchPostSave(id, userId, serverSaves);
if (res.error != undefined) {
return;
}
setServerSaves(res.success);
setServerSaved(res.success.includes(userId));
} catch (error: any) {
console.error("Failed to update save:", error.message);
}
};
return (
<div className="p-3 pl-0">
<div className="flex items-start">
<Link href={userURL} className="flex items-center gap-x-3">
<UserAvatar user={author} />
<div>
<p className="text-sm">{author.name}</p>
<p className="text-xs text-muted-foreground">
{formattedDate}
</p>
</div>
</Link>
</div>
<Link href={postURL}>
<AspectRatio ratio={2 / 2.75} className="mt-3 bg-muted">
<Image
src={image}
alt={id}
fill
className="object-cover rounded-lg"
/>
</AspectRatio>
</Link>
<div className="flex items-center justify-between gap-3 mt-3">
<div className="flex items-center gap-x-4">
<div className="flex items-center">
{userId !== undefined ? (
<Heart
className={`w-5 h-5 hover:cursor-pointer hover:opacity-70 ${
optimisticLiked
? "text-destructive fill-destructive"
: ""
}`}
onClick={handleLikeClick}
/>
) : (
<LoginModal>
<Heart className="w-5 h-5 hover:cursor-pointer hover:opacity-70" />
</LoginModal>
)}
<p className="ml-1 text-sm text-muted-foreground">
{optimisticLikes.length}
</p>
</div>
<div className="flex items-center">
<MessageCircle className="w-5 h-5" />
<p className="ml-1 text-sm text-muted-foreground hover:opacity-70">
{comments}
</p>
</div>
</div>
{userId !== undefined ? (
<Bookmark
className={`w-5 h-5 hover:cursor-pointer hover:opacity-70 ${
optimisticSaved
? "text-yellow-400 fill-yellow-400"
: ""
}`}
onClick={handleSaveClick}
/>
) : (
<LoginModal>
<Bookmark className="w-5 h-5 hover:cursor-pointer hover:opacity-70" />
</LoginModal>
)}
</div>
</div>
);
}
PostItem.Skeleton = function PostItemSkeleton() {
return (
<div>
<div className="flex items-start">
<div className="flex items-center gap-x-3">
<Skeleton className="w-12 h-12 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-4 w-[80px]" />
<Skeleton className="h-4 w-[125px]" />
</div>
</div>
</div>
<div>
<AspectRatio ratio={2 / 2.75} className="mt-3">
<Skeleton className="w-full h-full rounded-lg" />
</AspectRatio>
</div>
</div>
);
};
export default PostItem;
post.actions.ts:
"use server";
export async function patchPostLike(
postId: string,
userId: string,
likedBy: string[]
) {
const user = await getCurrentUser();
if (!user) return { error: "Unauthorized" };
try {
const isLiked = likedBy.some((like) => like === userId);
const post = await client.post.update({
where: {
id: postId,
},
data: {
likedBy: {
[isLiked ? "deleteMany" : "create"]: {
userId: userId,
},
},
},
include: {
likedBy: true,
},
});
const likes = post.likedBy.map((i) => {
return i.userId;
});
return { success: likes };
} catch (error: any) {
return { error: `Failed to add like to post: ${error.message}` };
}
}
export async function patchPostSave(
postId: string,
userId: string,
savedBy: string[]
) {
const user = await getCurrentUser();
if (!user) return { error: "Unauthorized" };
try {
const isSaved = savedBy.some((save) => save === userId);
const post = await client.post.update({
where: { id: postId },
data: {
savedBy: {
[isSaved ? "deleteMany" : "create"]: {
userId: userId,
},
},
},
include: { savedBy: true },
});
const saves = post.savedBy.map((i) => {
return i.userId;
});
return { success: saves };
} catch (error: any) {
return { error: `Failed to save post: ${error.message}` };
}
}
我尝试了各种方法,例如没有状态,而是将 useOptimistic 钩子的“后备”值作为通过 props 传入的值。但是,这不起作用,因为在从组件内部发出请求后,点赞将无法更新。serverLikes
likes
答: 暂无答案
上一个:findDOMNode 已弃用
下一个:为什么服务器操作不能并发执行?
评论