1

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.

Ray Purchase
  • 681
  • 1
  • 11
  • 37

1 Answers1

5

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.

Pranta
  • 2,928
  • 7
  • 24
  • 37
  • Hello Pranta, thank you for your answer. It's still the same as before, unfortunately. Is it because the form component which uses the above code isn't the component that displays the comments? The form component doesn't or didn't 'call' {data, mutate etc} = swr, the comments component did. Nevertheless I tried your code and variations of it but it still only updates on refresh – Ray Purchase Sep 25 '21 at 04:58
  • Is there any way you can reproduce the problem in codesandbox or stackblitz? – Pranta Sep 25 '21 at 05:54
  • Hello Pranta, sure, here's a basic reproduction of the component structure, https://codesandbox.io/s/adoring-dew-4fgz3 with – Ray Purchase Sep 25 '21 at 07:08
  • 1
    @MattHeslington now this should work. And always call the swr hook from the pages and not from the components. – Pranta Sep 25 '21 at 08:47
  • 1
    This is absolutely fantastic Pranta, thank you so much. And thank you for explaining the correct way to use swr too, I didn't know about only calling swr from pages, not components. Many, many thanks – Ray Purchase Sep 25 '21 at 09:11
  • @Pranta can you please check my issue here: https://stackoverflow.com/questions/70557556/client-side-data-fetching-with-swr-from-internal-api-route – Ilir Jan 02 '22 at 19:08