Reputation: 762
I have a form component where users can enter a comment, and a separate component which maps through and displays a list of comments. When a comment is submitted, it only gets displayed once the page loses and then regains focus. How can I have it so that the new comment is displayed when it is submitted without having to lose focus first?
This is the relevant code in my form component
import { useSWRConfig } from 'swr'
const { mutate } = useSWRConfig()
const postData = async (form) => {
setLoading(true);
await axios.post('/api/comments', form);
mutate('/api/comments');
setLoading(false);
}
According to the documentation https://swr.vercel.app/docs/mutation "You can get the mutate function from the useSWRConfig() hook, and broadcast a revalidation message globally to other SWR hooks using the same key by calling mutate(key)*"
This is the component which displays the comments:
const Comments = ({ postId }) => {
const { comments, loading, error } = useComments(postId)
if (error) return <div>An terror has occurred</div>;
if (loading) return <div>Loading comments</div>;
return (
<div className="flex flex-col w-full space-y-2">
{comments && comments[0].map((comment) => (
<Comment
id={comment._id}
key={comment._id}
date={comment.createdAt}
postId={comment._id}
comment={comment.comment}
name={comment.userData[0].name}
photoUrl={comment.userData[0].photoUrl}
/>
))}
</div>
)
}
And this is my useComments hook
export default function useComments (postId) {
const { data, mutate, error } = useSWR(`/api/comments/${postId}`, fetcher);
const loading = !data && !error;
const loggedOut = error && error.status === 403;
return {
loading,
loggedOut,
comments: data,
mutate,
error: error
}
}
Any help very, very much appreciated.
Upvotes: 2
Views: 9301
Reputation: 3695
The mutate function actually revalidates the cached result which means it sends another request to the API after you added the comment. You need to call the mutate function the following.
import { useSWRConfig } from 'swr'
const { data, mutate, error } = useSWR(`/api/comments`, fetcher);
const postData = async (form) => {
setLoading(true);
await axios.post('/api/comments', form);
mutate('/api/comments', {...data, form});
setLoading(false);
}
BTW, I am assuming form
contains the comment
object to save in the database. Let me know if this works.
So I found a solution for you. I don't know if it's perfect, but it works exactly how you want.
First pages/api/comments/index.js
case "GET":
try {
const { db } = await connectToDatabase();
const comments = await
db.collection("comments").find({}).toArray();
res.status(200).json({ comments });
} catch (error) {
console.log(error);
}
break;
We are returning an array of objects here inside a comment object.
Then in pages/index.js
file:
import Form from "../components/comment-form";
import Comments from "../components/comments";
import useSWR from "swr";
import fetcher from "../libs/fetcher";
export default function IndexPage() {
const { data, mutate } = useSWR(`/api/comments`, fetcher);
return (
<>
<div>hello</div>
<Form mutate={mutate} />
<Comments comments={data?.comments} />
</>
);
}
We are defining the useSWR hook here. And we only pass the mutate object from here.
Then components/comments.js
file
import Comment from "./comment";
const Comments = ({ comments }) => {
if (!comments) return <div>Loading comments</div>;
return (
<div>
<span>Comments</span>
{comments.map((comment, i) => (
<Comment key={i} comment={comment.comment} />
))}
</div>
);
};
export default Comments;
Here we are receiving the comments from the index file. By the way, this is the recommended way to fetch data by SWR.
Finally in the components/comment-form.js
file
import { useState } from "react";
import axios from "axios";
const AddComment = ({ mutate }) => {
const [form, setForm] = useState({
comment: ""
});
const postData = async (form) => {
await axios.post("/api/comments", form);
await mutate();
//clear the form
setForm({ ...form, comment: "" });
};
const handleSubmit = (e) => {
e.preventDefault();
postData(form);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={form.comment}
onChange={(e) => setForm({ ...form, comment: e.target.value })}
placeholder="Add a comment..."
/>
</form>
);
};
export default AddComment;
Now this will work. You can find the working code example in this sandbox-link
I really hope this will solve your problem.
Upvotes: 6