#2 - Creating a form and updating a page with router.refresh()

1 messages · Page 1 of 1 (latest)

stone coral
#

Please can you examine the code below and explain exactly when the router.refresh() is happening? I'm using the nextjs App router that was released with version 13 - not the pages router. This is a client component that exists within a server component. The server component is /topic/[topicPage]/page.tsx.

// components/AddResourceButton.tsx
'use client'

import React, { useState } from 'react';
import { BiPlus } from "react-icons/bi";
import AddResourceForm from '@/components/AddResourceForm';
import { useRouter } from 'next/navigation';

interface AddResourceButtonProps {
topicId: string;
}

const AddResourceButton: React.FC<AddResourceButtonProps> = ({ topicId }) => {
const [isFormVisible, setIsFormVisible] = useState(false);
const [resources, setResources] = useState([]);
const router = useRouter();

const fetchResources = async () => {
const res = await fetch(${process.env.NEXT_PUBLIC_API_BASE_URL}/topics/${topicId}/resources);
const data = await res.json();
setResources(data);
};

const handleFormSuccess = async () => {
setIsFormVisible(false);
await fetchResources();
router.refresh();
// Optionally refresh resources or show success message
};

return (
<>
{!isFormVisible && ( // This line controls the visibility of the button
<button
onClick={() => setIsFormVisible(true)}
className="flex items-center justify-center w-full bg-primary-600 text-text-50 font-semibold py-1 px-2 rounded-full hover:bg-primary-700 transition duration-300 ease-in-out focus:outline-none focus:shadow-outline"
>
<BiPlus className="mr-1 h-5 w-5" aria-hidden="true"/>
Add a Resource
</button>
)}

  {isFormVisible && <AddResourceForm topicId={topicId} userId="7bf0931d-69f6-4a1b-ab94-80b25c0f10ec" onSuccess={handleFormSuccess} />}
</>

);
};

export default AddResourceButton;

chrome ploverBOT
#

Hi @stone coral. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

The router.refresh() function is called within the handleFormSuccess function in your AddResourceButton component. This function is triggered when the form submission is successful.

Here's a step-by-step breakdown of what happens:

  1. When the "Add a Resource" button is clicked, the setIsFormVisible function is called with true as an argument. This changes the isFormVisible state to true, which in turn renders the AddResourceForm component.

  2. The AddResourceForm component receives handleFormSuccess as a prop for its onSuccess event. This means that when the form submission is successful, the handleFormSuccess function will be called.

  3. Inside handleFormSuccess, the setIsFormVisible function is called with false as an argument. This hides the form by changing the isFormVisible state to false.

  4. The fetchResources function is then called. This function fetches the updated list of resources from the server and updates the resources state with the new data.

  5. Finally, router.refresh() is called. This function is part of the Next.js Router API and is used to manually refresh the current route. It clears the Router Cache and makes a new request to the server for the current route, effectively updating the page with the new state.

In summary, router.refresh() is called after a successful form submission and after the updated resources have been fetched from the server. This ensures that the page reflects the most recent state of the data.

For more information on router.refresh(), you can refer to the Next.js documentation (https://nextjs.org/docs/app/building-your-application/caching#routerrefresh).

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@stone coral kapa.ai is still learning and improving, please let us know how it did by reacting below

stone coral
#

@chrome plover Do you think that the AddResourceButton code should work to update the resources list in this server component?

// imports

export async function generateStaticParams() {
const topics = await fetch(${process.env.NEXT_PUBLIC_API_BASE_URL}/topics).then(res => res.json())

return topics.map((topic: Topic) => ({
  topicName: encodeURIComponent(topic.name),
}))

}

const getResources = async (topicId: string) => {
const resources: Resource[] = await fetch(${process.env.NEXT_PUBLIC_API_BASE_URL}/topics/${topicId}/resources).then(res => res.json());
return resources;
};

const TopicPage = async ({ params }: { params: Params }) => {
const { topicName } = params;
const decodedTopicName = decodeURIComponent(topicName);
const topic = await fetch(${process.env.NEXT_PUBLIC_API_BASE_URL}/topics/${decodedTopicName}).then(res => res.json())
const resources = await getResources(topic.id);
// Sanitize the description HTML content
const sanitizedDescription = DOMPurify.sanitize(topic.description);

return (
<div >
{resources.map((resource) => (
<section key={resource.id} >
<div>
<div>
<div>H</div>
<div>
<span>Hugo Che</span>
<span>{resource.inserted ? formatDistanceToNow(new Date(resource.inserted), { addSuffix: true }) : 'Date not available'}</span>
</div>
</div>
</div>
<a href={resource.url} >
<BsLink45Deg />
{resource.url.length > 45 ? ${resource.url.substring(0, 45)}... : resource.url}
</a>
<p>{resource.description}</p>
</section>
))}
</div>
);
};

export default TopicPage;

I don't see how fetchResources in AddResourceButton is helping to update the resources list that is mapped out and displayed in the return function in TopicPage. I also don't know whether the router.refresh() function looks like it will interact with the component properly. Also I'm not sure whether there are other issues at hand. Can you please clarify? Remember I'm using the nextjs app router, not the pages router.

chrome ploverBOT
#

Let me check the knowledge base for you. One moment! :books:

#

Based on the provided code, it seems that the fetchResources function in the AddResourceButton component is fetching the updated list of resources and setting the local state of the AddResourceButton component. However, this updated state is not being passed back to the TopicPage component, so the TopicPage component is not aware of the updated resources.

The router.refresh() function in the AddResourceButton component is used to manually refresh the current route. This clears the Router Cache and makes a new request to the server for the current route. However, since the TopicPage component is not re-fetching the resources after the route refresh, the updated resources will not be reflected on the page.

To solve this issue, you could lift the state up to the TopicPage component and pass down a function to update the resources state to the AddResourceButton component. This way, when the resources are updated in the AddResourceButton component, the TopicPage component will also be aware of the updated resources.

However, this approach might not work as expected with the Next.js App Router, as it has a different caching behavior compared to the traditional Pages Router. The App Router caches the server components, and the cache is not invalidated when the state changes. This means that even if you update the state in the TopicPage component, the changes might not be reflected on the page because the server component is served from the cache.

#

Unfortunately, based on the provided extracts, it's not clear how to solve this issue with the Next.js App Router. You might need to look into the Next.js documentation or community resources for more information on how to handle state updates with the App Router.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@stone coral kapa.ai is still learning and improving, please let us know how it did by reacting below

stone coral
#

@chrome plover I wasn't expecting that response. I thought you would say: "You're correct that the fetchResources function in the AddResourceButton component is not directly updating the resources list in the TopicPage component. The fetchResources function is only updating the local state (resources) within the AddResourceButton component, which is not being used in the TopicPage component.

In the Next.js App Router, the recommended way to fetch and update data is by using the fetch function directly in the server component (TopicPage in this case). The router.refresh() function is used to trigger a refresh of the current route and re-fetch the data from the server."

And then I thought you would proceed to explain how I can do this. Do you agree this could be a good approach for the app router?

chrome ploverBOT
#

Let me check the knowledge base for you. One moment! :books:

#

Yes, you're correct. The fetchResources function in the AddResourceButton component is updating the local state (resources) within the AddResourceButton component, which is not being used in the TopicPage component.

In the Next.js App Router, the recommended way to fetch and update data is by using the fetch function directly in the server component (TopicPage in this case). The router.refresh() function is used to trigger a refresh of the current route and re-fetch the data from the server.

However, based on the provided code, it seems that the TopicPage component is not re-fetching the resources after the route refresh. This means that even though the router.refresh() function is called in the AddResourceButton component, the updated resources will not be reflected on the page.

To solve this issue, you could modify the TopicPage component to re-fetch the resources whenever the route is refreshed. This could be done by adding a useEffect hook that listens for changes in the route and calls the getResources function whenever the route changes.

Here's a simplified example of how you could do this:

import { useEffect } from 'react';
import { useRouter } from 'next/router';

// ...

const TopicPage = async ({ params }: { params: Params }) => {
  // ...

  const router = useRouter();

  useEffect(() => {
    // Call getResources whenever the route changes
    getResources(topic.id);
  }, [router.asPath]);

  // ...
};

This will ensure that the resources are re-fetched from the server whenever the route is refreshed, and the page will reflect the most recent state of the data.

#

@stone coral kapa.ai is still learning and improving, please let us know how it did by reacting below

stone coral
#

@chrome plover you say:

"However, based on the provided code, it seems that the TopicPage component is not re-fetching the resources after the route refresh."

Please can you explain what you see in the code that means that the component isn't refreshing the resources after the route refresh?

chrome ploverBOT
#

Let me check the knowledge base for you. One moment! :books:

#

In the provided TopicPage component code, there's no mechanism in place to re-fetch the resources when the route changes or refreshes. The resources are fetched once when the component is initially rendered, but there's no code to re-fetch the resources when the route changes.

Here's the relevant part of the TopicPage component:

const TopicPage = async ({ params }: { params: Params }) => {
  const { topicName } = params;
  const decodedTopicName = decodeURIComponent(topicName);
  const topic = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/topics/${decodedTopicName}`).then(res => res.json())
  const resources = await getResources(topic.id);
  // ...
};

In this code, getResources is called once when the TopicPage component is rendered. However, there's no mechanism in place to call getResources again when the route changes or refreshes.

In a typical React component, you could use the useEffect hook to re-run certain code when specific props or state values change. However, in a Next.js server component, hooks like useEffect are not available, and the component is re-rendered from the server side when the route changes or refreshes.

Therefore, to ensure that the resources are re-fetched when the route refreshes, you would need to implement a mechanism in your server or API to re-fetch the data whenever a request is made to the TopicPage route. This could involve setting appropriate cache headers in your server responses, or using a server-side data fetching strategy that supports revalidation, such as Incremental Static Regeneration (ISR) in Next.js.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@stone coral kapa.ai is still learning and improving, please let us know how it did by reacting below

stone coral
#

@chrome plover You say 'However, in a Next.js server component, hooks like useEffect are not available, and the component is re-rendered from the server side when the route changes or refreshes.' - that seems like it means that the data would refresh anyway if new data is posted to the resources table? Or is that not the case? I ask because I'm posting new data to the resources table just before I want a router.refresh().

chrome ploverBOT
#

Let me check the knowledge base for you. One moment! :books:

#

Yes, you're correct. In a Next.js server component, when a route change or refresh occurs, the component is re-rendered from the server side. This means that it will fetch the data again from the server, and if new data has been posted to the resources table, it should be included in the fetched data.

However, this behavior can be affected by various factors, such as caching strategies on the server or within Next.js itself. If the data is being cached and the cache is not invalidated when new data is posted, the old (cached) data might still be used even after a route refresh.

The router.refresh() function that you're calling in the AddResourceButton component should trigger a route refresh, which in turn should cause the TopicPage component to re-render and fetch the updated data from the server.

However, it's important to note that router.refresh() clears the Router Cache and makes a new request to the server for the current route, but it does not affect the Data or Full Route Cache (source (https://nextjs.org/docs/app/building-your-application/caching#routerrefresh)). This means that if the data is being cached elsewhere (e.g., in the Data Cache or Full Route Cache), the router.refresh() function might not have the desired effect.

If you're finding that the data is not being updated as expected after a route refresh, you might need to look into the caching behavior of your server or your Next.js application to ensure that the cache is being invalidated when new data is posted.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@stone coral kapa.ai is still learning and improving, please let us know how it did by reacting below

stone coral
#

@chrome plover how would I look into the caching behavior of my server or my Next.js application to ensure that the cache is being invalidated when new data is posted. I'm using Vercel.

chrome ploverBOT
#

Let me check the knowledge base for you. One moment! :books:

#

In Next.js, there are multiple layers of caching and multiple ways to invalidate different parts of the cache. Here are some ways you can manage and invalidate the cache:

  1. Router Cache: You can invalidate the Router Cache in a Server Action by revalidating data on-demand by path with revalidatePath or by cache tag with revalidateTag. Using cookies.set or cookies.delete also invalidates the Router Cache to prevent routes that use cookies from becoming stale. Calling router.refresh will invalidate the Router Cache and make a new request to the server for the current route (source (https://nextjs.org/docs/app/building-your-application/caching#invalidation-1)).

  2. Full Route Cache: You can invalidate the Full Route Cache by revalidating the Data Cache, which will in turn invalidate the Router Cache by re-rendering components on the server and caching the new render output. Unlike the Data Cache, which persists across deployments, the Full Route Cache is cleared on new deployments (source (https://nextjs.org/docs/app/building-your-application/caching#invalidation)).

  3. Vercel Data Cache: The Vercel Data Cache is unique per Vercel project, isolated per deployment environment (production or preview). Cached data is persisted across deployments, unless invalidated through revalidate or programmatically calling revalidateTag or revalidatePath. When a revalidation is triggered, the path or cache tag is marked stale globally in every Vercel Edge Network region. The next request to that path or tag, in any region, will trigger a revalidation and update the cache globally (source (https://vercel.com/docs/infrastructure/data-cache)).

#
  1. Purging Data Cache: In some circumstances, you may need to delete all cached data and force revalidation. You can do this by purging the Data Cache in your Vercel project settings (source (https://vercel.com/docs/infrastructure/data-cache/manage-data-cache)).

Remember that when you push a new Deployment to Vercel, the platform will purge the existing cache across all of its edge regions automatically. You will need to ensure that your proxy is also respecting this behavior, and purging its own proxy cache after each deployment (source (https://vercel.com/guides/can-i-use-a-proxy-on-top-of-my-vercel-deployment)).

If you're using a third-party API, you might also need to check the caching behavior of the API and ensure that it's not serving stale data.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: