Reputation: 4033
I have a list of posts, fetched with getServerSideProps
in the home page, then these posts are passed as props to a Posts
component, which maps those posts and pass each one to a Post
component. Pretty basic.
The problem starts here: Each post in the Post
component has this "Dropdown" with different options such as: Download, Save, Post Details and most importantly "Delete" if you are the user who actually posted that publication. What I'm trying to do is that when the user deletes his post, the changes get immediately reflected in the UI. Since deleting a post actually means removing it from the database and then showing the updated results from that database, I tried the following to refresh the server side props using getServerSideProps
:
This is the home page component:
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;
This is the Posts
component:
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;
I made this custom hook called useRefresh
in an attempt to refresh the server side props by using 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 };
};
And finally this is my Post
component:
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;
I have also tried to create a state in the Posts
component that gets populated with the posts list coming from the server as initialPosts
and then, creating a useEffect
that would set the posts
state with the new data coming from the server as soon as it changes, that way I could mutate that local state and the changes would be immediately reflected, but what happens is: When I try to delete the post, it gets removed from the UI but then it reappears again, which makes me think that getServerSideProps
is not getting the correct data, even though, when checking the database, that particular post has been successfully removed... Any insights? By the way, I'm using sanity as my backend.
If you would like to see another specific piece of code, please let me know and I'll edit the post.
Upvotes: 2
Views: 10816
Reputation: 4033
I finally found the root of the problem I described, and I thought it would be helpful to others who might trip into this same issue in the future if I answered this question.
So, the problem was in fact a cache issue, but not on the Nextjs' side, but on Sanity's side.
In my case, I need the freshest possible data since I'm building a social media app which of course, lets users do some actions that need to be reflected in the database and also in the UI as soon as possible.
So, if you're using Sanity and you need the freshest possible data, make sure to use the live, uncached API instead of the API CDN. If you're using Sanity client
just put this into the config object: useCdn: false
.
Upvotes: 0
Reputation: 76
First of all I think better approach overall would be to use useState
to store posts client side and then to refresh data the simplest way would be just to make API call to fetch post list.
getServerSideProps
is meant to be used for filling initial page data, main purpose of that is so search engine crawlers can gather more data and your page gets better SEO score. It should not be your main way of refreshing data UX wise.
With all that being said the problem you are having could be due to caching in NextJS development mode (npm run dev
). Try building the project and starting it in production mode and see if that helps.
Upvotes: 1