#Cache revalidation with server actions

1 messages · Page 1 of 1 (latest)

tepid shadow
#

Consider this component:

const MicrositesTable = async () => {
const response = await fetch(URL + '/api/microsites', {
next: {
tags: ["microsites"],
},
});
const microsites = await response.json()
const data = await buildMicrositesTableData(microsites);

return (
<TableList
name={name}
headers={headers}
data={data}
actionButtons={actionButtons}
/>
);
};

export default MicrositesTable;

I use this NewMicrositeButton component to create a new microsite with an API call:

const NewMicrositeButton = () => {
const create = async () => {
'use server'
try {
const response = await fetch(URL + '/api/microsites', {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ data: newMicrosite })
})
const microsite = await response.json();
const micrositeId = microsite?.id;
if (!micrositeId) return;
revalidateTag('microsites');
} catch (error) {
console.error(error);
}
};

return (
<form action={create}>
<button
className="px-2 py-1 rounded-lg bg-gray-100"
>
<div className="flex items-center space-x-2">
<FaPlus />
<p className="font-bold">
{"New"}
</p>
</div>
</button>
</form>
);
};

export default NewMicrositeButton;

However I do not see the updated fetch for "microsites" unless I do a hard refresh on my browser

thick lynxBOT
#

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

#

The issue you're experiencing might be due to the way you're handling data revalidation in your NewMicrositeButton component. In Next.js, the revalidateTag function is used to revalidate all entries associated with a specific cache tag. This function is part of the Next.js cache tagging system, which is used for invalidating fetch requests across routes.

In your NewMicrositeButton component, you're calling revalidateTag('microsites') after creating a new microsite. This should, in theory, revalidate all data associated with the 'microsites' tag. However, it's important to note that revalidateTag is an asynchronous operation, and it might not complete immediately.

Here's the relevant code from your NewMicrositeButton component:

const create = async () => {
  'use server'
  try {
    const response = await fetch(URL + '/api/microsites', {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ data: newMicrosite })
    })
    const microsite = await response.json();
    const micrositeId = microsite?.id;
    if (!micrositeId) return;
    revalidateTag('microsites');
  } catch (error) {
    console.error(error);
  }
};

You might want to ensure that the revalidateTag function has completed before proceeding. You can do this by awaiting the revalidateTag function, like so:

await revalidateTag('microsites');

This will ensure that the data associated with the 'microsites' tag is revalidated before the function completes.

If you're still experiencing issues, it might be worth checking the network tab in your browser's developer tools to see if the fetch request is being made as expected.

#

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

tepid shadow
#

@thick lynx I know that the fetch operation is completing successfully. I do not see the fresh data unless I do a hard refresh. Not even a router.refresh() works to get the fresh data

thick lynxBOT
#

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

#

Based on the information provided, it seems like the issue might be related to the way caching is handled in Next.js. When you use fetch with the next option, it caches the data. The revalidateTag function invalidates the cache, but it doesn't immediately refresh the data on the client side.

The router.refresh() function, as per the Next.js documentation (https://nextjs.org/docs/app/api-reference/functions/use-router), is used to manually refresh a route. This completely clears the Router Cache and makes a new request to the server for the current route. However, refresh does not affect the Data or Full Route Cache. This means that if your data is cached, router.refresh() might not fetch the new data.

To immediately invalidate the Data Cache and Router cache, you can use revalidatePath or revalidateTag in a Server Action (https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions).

Here's how you can use revalidatePath:

await revalidatePath('/api/microsites');

This will revalidate the Data Cache for the specified path, which in turn invalidates the Full Route Cache. This should fetch the new data and re-render the route segments below the specified path.

Remember, revalidatePath is different from router.refresh(). The revalidatePath function purges the Data Cache and Full Route Cache, whereas router.refresh() does not change the Data Cache and Full Route Cache, as it is a client-side API.

For more information on data fetching and revalidation in Next.js, you can refer to the Next.js documentation (https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating).

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

#

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

tepid shadow
#

The page.tsx file for microsites exists at /microsites and I do the fetch operations at /api/microsites, which one should I refresh to ensure I see fresh data on the /microsites page? Note that the NewMicrositesButton and the MicrositeTable components both get rendered at the /microsites page

#

@thick lynx The page.tsx file for microsites exists at /microsites and I do the fetch operations at /api/microsites, which one should I refresh to ensure I see fresh data on the /microsites page? Note that the NewMicrositesButton and the MicrositeTable components both get rendered at the /microsites page

thick lynxBOT
#

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

#

To ensure you see fresh data on the /microsites page, you should revalidate the /microsites path. This is because the /microsites path is where your MicrositesTable component is rendered, and this is the component that displays the data fetched from /api/microsites.

Here's how you can use revalidatePath:

await revalidatePath('/microsites');

This will revalidate the Data Cache for the /microsites path, which in turn invalidates the Full Route Cache. This should fetch the new data and re-render the route segments below the /microsites path.

Remember, revalidatePath is different from router.refresh(). The revalidatePath function purges the Data Cache and Full Route Cache, whereas router.refresh() does not change the Data Cache and Full Route Cache, as it is a client-side API.

For more information on data fetching and revalidation in Next.js, you can refer to the Next.js documentation (https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating).

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

#

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

tepid shadow
#

@thick lynx How can I confirm that my cache is being invalidates and my app is fetching fresh data again? Can I add a console.log to the GET route handler that's using the "microsites" tag?

thick lynxBOT
#

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

#

Yes, you can add a console.log statement to your GET route handler to confirm that the cache is being invalidated and fresh data is being fetched. This can be a simple way to debug and ensure that your data revalidation is working as expected.

Here's an example of how you can do this:

export default async function handler(req, res) {
  const response = await fetch(URL + '/api/microsites', {
    next: {
      tags: ["microsites"],
    },
  });
  const data = await response.json();

  console.log('Data fetched:', data);

  res.status(200).json(data);
}

In this example, the console.log statement will log the data fetched from /api/microsites every time the handler is called. If your data revalidation is working correctly, you should see this log every time the data is revalidated.

Remember to remove or comment out the console.log statement once you're done debugging, as it can slow down your application and use up your server's resources.

For more information on data fetching and revalidation in Next.js, you can refer to the Next.js documentation (https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating).

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

#

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

tepid shadow
#

@thick lynx Here is my GET handler:

`export const GET = async () => {
const microsites = await fetchEntriesFromCollection("events");
console.log("Fetched", microsites.length, "microsites");

return new NextResponse(JSON.stringify(microsites), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
};`

Notice the console.log. I now see when I run my server action and call revalidateTag("microsites") that my server logs "Fetched 2 microsites" when there was only one before. Every time I call the server action, this number goes up by one, which is expected. However, the number of microsites rendered in my Microsites table remains the same:

`const MicrositesTable = async () => {
const response = await fetch(URL + '/api/microsites', {
next: {
tags: ["microsites"],
},
});
const microsites = await response.json()
const data = await buildMicrositesTableData(microsites);

return (
<TableList
name={name}
headers={headers}
data={data}
actionButtons={actionButtons}
/>
);
};

export default MicrositesTable;
`

thick lynxBOT
#

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

#

The issue you're experiencing might be due to the way your MicrositesTable component is structured. In React, components are not re-rendered unless their state or props change. In your case, the MicrositesTable component fetches the data when it's first rendered, but it doesn't fetch the data again when the cache is invalidated.

To solve this issue, you can use a state variable to store your data and an effect hook to fetch the data whenever the cache is invalidated. Here's an example of how you can do this:

import { useState, useEffect } from 'react';

const MicrositesTable = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(URL + '/api/microsites', {
        next: {
          tags: ["microsites"],
        },
      });
      const microsites = await response.json();
      const tableData = await buildMicrositesTableData(microsites);
      setData(tableData);
    };

    fetchData();
  }, []);

  return (
    <TableList
      name={name}
      headers={headers}
      data={data}
      actionButtons={actionButtons}
    />
  );
};

export default MicrositesTable;

In this example, the useEffect hook fetches the data whenever the component is rendered. The setData function updates the state, which causes the component to re-render.

Please note that this is a simplified example and might not cover all cases. You might need to adjust it to fit your specific use case.

For more information on data fetching and revalidation in Next.js, you can refer to the Next.js documentation (https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating).

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

#

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

tepid shadow
#

@thick lynx Okay, how about in this example:

import Section from "@/app/components/Section";
import { MicrositeSection } from "@/enums";
import ProductListsDropdown from "./ProductListDropdown";
import { API } from "@/utils/constants";

const ProductListSection = async () => {
const res = await fetch(API + '/productLists', {
next: {
revalidate: 5,
tags: ["productLists"],
},
});
const productLists = await res.json();

const defaultProductList = { name: 'NONE' } as ProductList;
const includeNone = [defaultProductList, ...productLists];

return (
<Section
id={MicrositeSection.Products}
title={"Product List"}
completed={false}>
<ProductListsDropdown
productLists={includeNone}
/>
</Section>
);
};

export default ProductListSection;

The productLists are created on a different application, so I need to refetch them every 5 seconds to populate my ProductListDropdown. But it seems I am not refetching every 5 seconds like I thought.

here is the route:

export const GET = async () => {
const productLists = await fetchEntriesFromCollection("event-product-lists") as ProductList[];
console.log("refetching product lists")

return new NextResponse(JSON.stringify(productLists), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
};

I only see "refetching product lists" get logged once, and never again.

thick lynxBOT
#

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