Ray Purchase
Ray Purchase

Reputation: 762

How to refresh a data feed instantly using SWR in Next.js

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

Answers (1)

Pranta
Pranta

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.

Edited

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

Related Questions