4

I have a protected dashboard page that contains a list of reviews. When a new review is added, I want to refresh the page. However, this does not work. Specifically, useRouter().refresh does nothing. I have an AddReview component that is displayed on the dashboard page. It's a button that, when clicked, triggers a modal that allows the user to enter review data.

Here is the code for the AddReview component:

"use client";
import { AiOutlinePlus } from 'react-icons/ai';
import Modal from './Modal.js';
import { useState } from 'react';
import { addReview, getAllReviews } from '/api';
import { useRouter } from "next/navigation";
import Router from 'next/navigation';
import { useSession } from 'next-auth/react';
import { v4 as uuidv4 } from 'uuid';

export default function AddReview()
{
    const { data: session } = useSession();
    const router = useRouter();
    const [modalOpen, setModalOpen] = useState(false);
    const [newContentCreatorName, setNewContentCreatorName] = useState("");
    const [newRating, setNewRating] = useState("");
    const [newText, setNewText] = useState("");

    const handleSubmitNewTodo = async (e) => {
        e.preventDefault();
                // API to add review
        await addReview({
            id: uuidv4(),
            contentCreatorName: newContentCreatorName,
            rating: newRating,
            text: newText,
            owner: session.user.email,
        });

        setNewContentCreatorName("");
        setNewRating("");
        setNewText("");
        setModalOpen(false);
        router.refresh(); // refresh page to show new item in ReviewList
    };

    return (
    <div>
        <button 
            onClick={() => setModalOpen(true)} 
            className="btn btn-primary w-full">
                Add new review <AiOutlinePlus className="ml-2" size={20}/>
        </button>
        <Modal modalOpen={modalOpen} setModalOpen={setModalOpen}>
            <form onSubmit={handleSubmitNewTodo} className="w-full max-w">
                <h3 className="font-bold text-lg mb-6 text-center">Add review</h3>
                <div>
                    <input 
                        value={newContentCreatorName} 
                        onChange={e => setNewContentCreatorName(e.target.value)} 
                        type="text" 
                        placeholder="Content creator name" 
                                className="block input input-bordered w-full mb-4" />
                    <input 
                        value={newRating} 
                        onChange={e => setNewRating(e.target.value)} 
                        type="number" 
                        placeholder="Rating (out of 5)" 
                                className="block input input-bordered w-full mb-4" />
                    <input 
                        value={newText} 
                        onChange={e => setNewText(e.target.value)} 
                        type="text" 
                        placeholder="Review" 
                                className="block input input-bordered w-full mb-4" />

                    <button className="btn" type="submit">Submit</button>
                </div>
            </form>
        </Modal>
    </div>
    );
}

I am using the session object so that I can record the owner/author of each review as it's added to the databse. On the Dashboard page, there is also a session object that is used to validate if the user is logged in. Here is the code for the Dashboard page.

"use client";

import AddReview from '../components/AddReview';
import ReviewList from '../components/ReviewList';

import { getAllReviews } from '/api';
import { use } from 'react';

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

const _reviews = getAllReviews(); // API to fetch reviews

export default function Dashboard()
{
    const reviews = use(_reviews);
    console.log(reviews);

    const { data: session, status } = useSession({
        required: true,
      });
    if (status === "loading")
      return <div>Session is loading...</div>;

    return (
        <>
            <div className="text-center my-5 flex flex-col gap-4">
                <h1 className="text-2xl font-bold">Yelper</h1>
                <AddReview />
            </div>
                        {/* A component showing list of reviews (should be rerendered to reflect new review added) */}
            <ReviewList reviews={reviews} email={session?.user.email} />
        </>   
    );
}

What should I do to have the useRouter().refresh function actually refresh the page?

EDIT: Here's the function that I call to add a new review, in case that gives more context:

export async function addReview(review)
{
    const res = await fetch(`${baseUrl}/reviews`, {
        method: "POST",
        headers:  {
            "Content-Type": "application/json",
        },
        body: JSON.stringify(review).trim()
    });
    const newReview = await res.json();
    return newReview;
}
lost_in_the_source
  • 10,998
  • 9
  • 46
  • 75

3 Answers3

0

Your dashboard page isn't refreshing because you are using a client component that doesn't involve any server-side data fetching. This is the expected behavior based on Next.js documentation:

router.refresh(): Refresh the current route. Making a new request to the server, re-fetching data requests, and re-rendering Server Components. The client will merge the updated React Server Component payload without losing unaffected client-side React (e.g. useState) or browser state (e.g. scroll position).

According to your current implementation, you are fetching the list of reviews only once on initial page render.

You can achieve the desired behavior in several different ways.

  1. Refactor your Dashboard page into a server component and fetch data server-side - https://nextjs.org/docs/app/building-your-application/data-fetching/fetching

  2. Store the list of reviews in a state (useState), and re-fetch the data when a new review is added.

I hope this helps.

Igor Danchenko
  • 1,980
  • 1
  • 3
  • 13
0

I think that is because you have a server component which renders the client component AddReview. when you call router.refresh you should actually tell server component to make a new request and get the latest data. for this use useTransitionHook.

import {  useTransition } from 'react';

  
export default function AddReview()
{

const [isPending, startTransition] = useTransition();


    const handleSubmitNewTodo = async (e) => {
         ...
         ...

         startTransition(()=>{
              router.refresh()
         })    
    }
}
  • your current fetch request is cached. you might need to use no-store option to get always the fresh data

this explains useTransition

Before React 18, rendering was synchronous. This meant that once React started rendering, nothing could stop it until it completed rendering the component. However, with concurrent rendering, React can pause the rendering and continue with it later or abort the rendering altogether. This is an important new implementation that allows us to provide a more fluid user experience to our users.

We can use the useTransition hook to tell React that a certain state change will result in an expensive rendering. React will then deprioritize this state change allowing other renderings to take place faster providing a very responsive UI. Such expensive renderings are called transition updates and the ones that should take place immediately are called urgent updates. Usually, typing, clicking, and pressing are considered urgent updates as they should provide users with an immediate response to ensure a good user experience.

  • or you could try revalidatePath

     import { revalidatePath } from "next/cache";
    
     const handleSubmitNewTodo = async (e) => {
           ...
           ...
    
           // this also refreshes the page
           revalidatePath("/");
      }
    
Yilmaz
  • 35,338
  • 10
  • 157
  • 202
0

I think this issue is because of context/scope. You are calling handleSubmitNewTodo from inside the Modal's form. When it's called from there, router's context is lost. Try passing the instance of router to the Modal and receive it back from the modal like this.
Save instance of router in the Modal

 const handleSubmitNewTodo = async (e, routerInstance) => {
        e.preventDefault();
        ...
       routerInstance.refresh(); 
 ....
 ....

<form onSubmit={(e) => {handleSubmitNewTodo(e,routerInstance}} className="w-full max-w">

If the above doesn't works, then try creating an instance of router from inside the Modal and pass it to handleSubmitNewTodo during the call.

Mearaj
  • 1,828
  • 11
  • 14