刷新服务器端道具 - Nextjs

Refreshing Server side props - Nextjs

我有一个 post 的列表,在主页中使用 getServerSideProps 获取,然后这些 post 作为道具传递给 Posts 组件,它映射那些 posts 并将每个传递给 Post 组件。很基础。

问题从这里开始:Post 组件中的每个 post 都有这个带有不同选项的“下拉菜单”,例如:下载、保存、Post 详细信息以及最重要的“删除” " 如果您是实际 post 编辑该出版物的用户。我想要做的是,当用户删除他的 post 时,更改会立即反映在 UI 中。由于删除 post 实际上意味着将其从数据库中删除,然后显示该数据库的更新结果,我尝试了以下使用 getServerSideProps:

刷新服务器端道具

这是主页组件:

import React from "react";
import { Layout, Posts } from "../components";
import { client } from "../client/client";
import { postsQuery } from "../utils/data";

import { getSession } from "next-auth/react";

const Home = ({ posts }) => {
  return (
    <Layout>
      <section className="w-full px-4 py-4 md:px-8 lg:px-10">
        <Posts posts={posts} />
      </section>
    </Layout>
  );
};

export async function getServerSideProps(context) {
  const session = await getSession(context);

  if (!session) {
    return {
      redirect: {
        destination: "/login",
        permanent: false,
      },
    };
  }

  const doc = {
    _type: "user",
    _id: session.user.uid,
    userName: session.user.name,
    image: session.user.image,
    userTag: session.user.tag,
  };

  await client.createIfNotExists(doc);
  const query = postsQuery();
  const posts = await client.fetch(query);

  return {
    props: {
      posts,
    },
  };
}

export default Home;

这是 Posts 组件:

import React from "react";
import { Post } from "./index";
import { useRefresh } from "../hooks/useRefresh";
import Masonry from "react-masonry-css";
import { Loading } from "../components";

const breakpointColumnsObj = {
  default: 5,
  3000: 5,
  2000: 4,
  1600: 3,
  1200: 2,
  640: 1,
};

const Posts = ({ posts }) => {
  const { isRefreshing, refreshData } = useRefresh(posts);

  return (
    <Masonry
      className="flex w-full h-full"
      breakpointCols={breakpointColumnsObj}
    >
      {posts?.map((post) => (
        <Post key={post._id} post={post} refreshData={refreshData} />
      ))}
      {isRefreshing && <Loading />}
    </Masonry>
  );
};

export default Posts;

我制作了这个名为 useRefresh 的自定义挂钩,试图通过使用 router.replace:

刷新服务器端道具
import { useState, useEffect } from "react";
import { useRouter } from "next/router";

export const useRefresh = (changingValue) => {
  const [isRefreshing, setIsRefreshing] = useState(false);
  const router = useRouter();

  const refreshData = () => {
    router.replace(router.asPath);
    setIsRefreshing(true);
  };

  useEffect(() => {
    setIsRefreshing(false);
  }, [changingValue]);

  return { isRefreshing, refreshData };
};

最后这是我的 Post 组件:

import React, { useState } from "react";
import Image from "next/image";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";

import ConfirmModal from "./ConfirmModal";
import Dropdown from "./Dropdown";
import axios from "axios";
import { HiOutlineChevronDown, HiOutlineChevronUp } from "react-icons/hi";
import { toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";

const Post = ({ post, refreshData }) => {
  const { data: session } = useSession();
  const [dropdownOpen, setDropdownOpen] = useState(false);
  const [openModal, setOpenModal] = useState(false);
  const [saving, setSaving] = useState(false);
  const [unsaving, setUnsaving] = useState(false);

  const router = useRouter();

  const alreadySaved = post?.saved?.filter(
    (item) => item.postedBy?._id === session?.user?.uid
  );

  const deletePost = (postId) => {
    axios
      .post("/api/posts/deletePost", {
        postId: postId,
      })
      .then(() => {
        toast.success("Post deleted!");
      })
      .catch((error) => {
        toast.error(
          `Couldn't delete the post due to an error: ${error.message}`
        );
      });
  };

  const saveOrUnsavePost = (postId) => {
    if (alreadySaved?.length === 0) {
      setSaving(true);
      axios
        .post(
          `/api/posts/saveOrUnsavePost?postId=${postId}&userId=${session.user.uid}&action=save`
        )
        .then(() => {
          setSaving(false);
          setDropdownOpen(false);
          toast.success("Post saved!");
        })
        .catch((error) => {
          setSaving(false);
          toast.error(
            `There was an error saving this post: ${error.message}... Try again.`
          );
        });
    }
    if (alreadySaved?.length > 0) {
      setUnsaving(true);
      axios
        .post(
          `/api/posts/saveOrUnsavePost?postId=${postId}&userId=${session.user.uid}&action=unsave`
        )
        .then(() => {
          setUnsaving(false);
          setDropdownOpen(false);
          toast.success("Post Unsaved!");
        })
        .catch((error) => {
          setUnsaving(false);
          toast.error(
            `There was an error unsaving this post: ${error.message}... Try again.`
          );
        });
    }
  };

  return (
    <>
      <div className="relative p-3 transition duration-300 ease-in-out hover:shadow-md hover:shadow-gray-600">
        <div className="flex items-center justify-between w-full mb-3">
          <div
            className="flex items-center gap-2 cursor-pointer"
            onClick={() => router.push(`/user/${post.postedBy._id}`)}
          >
            <img
              src={post?.postedBy?.image}
              alt="User avatar"
              className="object-cover w-8 h-8 rounded-full"
            />
            <div className="text-sm font-bold text-white 2xl:text-base">
              {post?.postedBy?.userTag}
            </div>
          </div>
          <div
            className="flex items-center justify-center w-5 h-5 transition duration-150 ease-in-out bg-white rounded-sm cursor-pointer hover:bg-gray-200"
            onClick={() => setDropdownOpen(!dropdownOpen)}
          >
            {dropdownOpen ? (
              <HiOutlineChevronUp fontSize={16} />
            ) : (
              <HiOutlineChevronDown fontSize={16} />
            )}
          </div>
        </div>
        {dropdownOpen && (
          <Dropdown
            setDropdownOpen={setDropdownOpen}
            setOpenModal={setOpenModal}
            postedBy={post?.postedBy}
            postImage={post?.image}
            postId={post?._id}
            saving={saving}
            unsaving={unsaving}
            saveOrUnsavePost={saveOrUnsavePost}
            alreadySaved={alreadySaved}
            refreshData={refreshData}
          />
        )}
        <div className="relative post__image-container">
          <Image
            src={post?.image?.asset?.url}
            placeholder="blur"
            blurDataURL={post?.image?.asset?.url}
            layout="fill"
            className="rounded-lg post__image"
            alt="post"
          />
        </div>
        <p className="mt-3 text-sm text-white 2xl:text-base">{post.title}</p>
      </div>

      {openModal && (
        <ConfirmModal
          setOpenModal={setOpenModal}
          deletePost={deletePost}
          postId={post?._id}
          refreshData={refreshData}
        />
      )}
    </>
  );
};

export default Post;

我还尝试在 Posts 组件中创建一个状态,该状态由来自服务器的 posts 列表作为 initialPosts 填充,然后创建一个 useEffect 将设置 posts 状态与来自服务器的新数据,一旦它发生变化,这样我就可以改变本地状态并且变化会立即反映出来,但发生的事情是:当我尝试删除 post,它从 UI 中删除,但随后再次出现,这让我认为 getServerSideProps 没有获得正确的数据,即使在检查数据库时也是如此,那个特定的 post 已被成功删除......有什么见解吗?顺便说一下,我正在使用 sanity 作为我的后端。

如果您想查看另一段特定代码,请告诉我,我将编辑 post。

首先,我认为总体上更好的方法是使用 useState 存储 post 的客户端,然后刷新数据,最简单的方法就是使 API调用以获取 post 列表。

getServerSideProps 用于填充初始页面数据,主要目的是让搜索引擎爬虫可以收集更多数据,让您的页面获得更好的 SEO 分数。它不应该是您刷新数据 UX 明智的主要方式。

综上所述,您遇到的问题可能是由于 NextJS 开发模式 (npm run dev) 中的缓存所致。尝试构建项目并以生产模式启动它,看看是否有帮助。

我终于找到了我描述的问题的根源,如果我回答了这个问题,我认为对以后可能遇到同样问题的其他人会有帮助。

所以,问题实际上是缓存问题,但不是在 Nextjs 方面,而是在 Sanity 方面。

就我而言,我需要尽可能最新的数据,因为我正在构建一个社交媒体应用程序,当然,它可以让用户执行一些需要反映在数据库和 UI 中的操作尽快。

因此,如果您正在使用 Sanity 并且需要尽可能最新的数据,请确保使用实时的、未缓存的 API 而不是 API CDN。如果您使用的是 Sanity client,只需将其放入配置对象中:useCdn: false.