Next.js Discord

Discord Forum

fetch caching isn't revalidating data

Unanswered
Spectacled Caiman posted this in #help-forum
Open in Discord
Spectacled CaimanOP
I may be implementing this incorrectly, however I want this data to be re-fetched after a minute however it currently doesn't refetch it only fetches once.
// Server Action
'use server';

export async function fetchFeaturedServers(): Promise<APIGuildData[]> {
    const response = await fetch(process.env.API_ENDPOINT + 'servers/featured', {
        next: {
            revalidate: 1 * 60 * 1_000
        }
    });

    if (!response.ok) return [];

    const data = await response.json();
    if (typeof data === 'undefined' || !Array.isArray(data)) return [];

    return data;
}

// Client
const [state, featuredServers] = useAsyncFn(() => fetchFeaturedServers());

36 Replies

There are a lot of things which are wrong with this tbh.
1. If you want to revalidate every minute just put revalidate: 60, it's in seconds not ms
2. The way you've implement it so far, I don't think it's going to work, instead you're either going to have to switch to a server component and do the fetch in there, with the revalidate in which case it will work, or turn the server action into a route handler and use fetch in a useEffect in the client with a revalidate and a setState.
@Spectacled Caiman
@Jesse There are a lot of things which are wrong with this tbh. 1. If you want to revalidate every minute just put `revalidate: 60`, it's in seconds not ms 2. The way you've implement it so far, I don't think it's going to work, instead you're either going to have to switch to a server component and do the fetch in there, with the revalidate in which case it will work, or turn the server action into a route handler and use fetch in a useEffect in the client with a revalidate and a setState.
Spectacled CaimanOP
Ah I didn't know it was seconds. As for the way I implemented, I may be confused between server actions and server components. An issue I've noticed with this is that I want a skeleton loader to be displayed when making this call to this server function however when I refresh the page, it doesn't display the loading skeletons until a short while after where then the actual data is instantly loaded straight after so I think that it is waiting for the data to be displayed
If you want skeleton as well, then you either want to use Suspense, or conditional rendering to see if the fetch is finished yet
Spectacled CaimanOP
I'll show you my client side implementation,
"use client";

...imports...

export default function ServersPage() {
  const [showBg, setShowBg] = useState<boolean>(false);
  const searchParams = useSearchQuery();
  const [search] = searchParams;
  const s = useSearch(search);

  const [results, setResults] = useState<APIGuildData[]>([]);
  const [state, featuredServers] = useAsyncFn(() => fetchFeaturedServers());

  useEffect(() => {
    async function fetchData() {
      const servers = await featuredServers();
      setResults(servers);
    }

    fetchData();
  }, [featuredServers]);

  return (
    <>
      <FooterView>
        <Navigation bg={showBg} />
        <div className="mb-16 sm:mb-24">
          <HeroPart
            searchParams={searchParams}
            setIsSticky={setShowBg}
          ></HeroPart>
        </div>
        <WideContainer ultraUltraWide>
          {s.loading ? (
            <SearchLoadingCard />
          ) : s.searching ? (
            <SearchServers
              searchQuery={search}
            />
          ) : state.loading ? (
            <SearchLoadingCard /> // The skeletons for the api call
          ) : (
            // The results for the api call
            <div className="grid grid-cols-1 gap-6 lg:grid-cols-4">
              {results.map((result, index) => (
                <ServerCard key={index} guild={result} />
              ))}
            </div>
          )}
        </WideContainer>
      </FooterView>
    </>
  );
}
If you used Condition rendering, it'd look something like this.
"use client"

import { useState, useEffect } from "react"

export default function Component() {
  const [fetchedValue, setFetchedValue] = useState()

  useEffect(() => {
    (async() => {
      const res = await fetch("...", {
        next: {
          revalidate: 60
        }
      })
      const data = await res.json()
      setFetchedValue(data)
    })()
  })

  return (
    <div>
      {fetchedValue ? (
        <MyContent />
      ) : (
        <Skeleton />
      )}
    </div>
  )
}
That will work
you could also use a setInterval if you really wanna use a server action
Could use promise.then instead of an Async IIFE as well
as for your implementation it won't work because it's only gonna refetch once featuredServers changes
Spectacled CaimanOP
ill try remove the useAsyncFn
"use client";

...imports...

export default function ServersPage() {
  const [showBg, setShowBg] = useState<boolean>(false);
  const searchParams = useSearchQuery();
  const [search] = searchParams;
  const s = useSearch(search);

  const [results, setResults] = useState<APIGuildData[]>([]);
  const [state, featuredServers] = useState()

  useEffect(() => {
    async function fetchData() {
      const res = await fetch("...", {
        next: {
          revalidate: 60
        }
      })
      const servers = await res.json()
      setResults(servers);
    }

    fetchData();
  }, [featuredServers]);

  return (
    <>
      <FooterView>
        <Navigation bg={showBg} />
        <div className="mb-16 sm:mb-24">
          <HeroPart
            searchParams={searchParams}
            setIsSticky={setShowBg}
          ></HeroPart>
        </div>
        <WideContainer ultraUltraWide>
          {s.loading ? (
            <SearchLoadingCard />
          ) : s.searching ? (
            <SearchServers
              searchQuery={search}
            />
          ) : state.loading ? (
            <SearchLoadingCard /> // The skeletons for the api call
          ) : (
            // The results for the api call
            <div className="grid grid-cols-1 gap-6 lg:grid-cols-4">
              {results.map((result, index) => (
                <ServerCard key={index} guild={result} />
              ))}
            </div>
          )}
        </WideContainer>
      </FooterView>
    </>
  );
}
I think that should work but test
@Jesse If you used Condition rendering, it'd look something like this. tsx "use client" import { useState, useEffect } from "react" export default function Component() { const [fetchedValue, setFetchedValue] = useState() useEffect(() => { (async() => { const res = await fetch("...", { next: { revalidate: 60 } }) const data = await res.json() setFetchedValue(data) })() }) return ( <div> {fetchedValue ? ( <MyContent /> ) : ( <Skeleton /> )} </div> ) }
Spectacled CaimanOP
in this, you put the fetch in the actual client side whereas i do this:
// src/components/servers/ServersPage.tsx
"use client";

import { fetchFeaturedServers } from "../actions/fetchData";

export default function ServersPage() {
  const [results, setResults] = useState<APIGuildData[]>([]);

  useEffect(() => {
    (async () => {
      const servers = await fetchFeaturedServers();
      setResults(servers);
    })();
  }, []);

// src/components/actions/fetchData.ts
'use server';

const fetchOptions: RequestInit = {
    next: {
        revalidate: 60
    }
}

export async function fetchFeaturedServers(): Promise<APIGuildData[]> {
    const response = await fetch(process.env.API_ENDPOINT + '/servers/featured', fetchOptions);

    if (!response.ok) return [];

    const data = await response.json();
    if (typeof data === 'undefined' || !Array.isArray(data)) return [];

    return data;
}


is there a difference between these implementations?
I believe so, but you'd have to remove the "use server", as I don't think using a server action here will work, is there any requirement to fetch on the server
@Jesse I believe so, but you'd have to remove the "use server", as I don't think using a server action here will work, is there any requirement to fetch on the server
Spectacled CaimanOP
this is my first time using next so I thought it made more sense and looks cleaner having it in a separate file but im not sure. it seems to work (the data is being returned)
it will always return the data I just don't think the revalidate logic will work, but try it and see what happens
Spectacled CaimanOP
this is my current implementation, the data is logged the only issue is that the actual content on the page isn't being refreshed
  const [featuredServers, setFeaturedServers] = useState<APIGuildData[]>([]);

  useEffect(() => {
    (async () => {
      const servers = await fetchFeaturedServers();
      console.log(servers)
      setFeaturedServers(servers);
    })();
  }, []);


...
          featuredServers.length ? (
            <SearchLoadingCard />
          ) : (
            <div className="grid grid-cols-1 gap-6 lg:grid-cols-4">
              {featuredServers.map((result, index) => (
                <ServerCard key={index} guild={result} />
              ))}
            </div>
          )}
@Jesse it will always return the data I just don't think the revalidate logic will work, but try it and see what happens
Spectacled CaimanOP
revalidating seems to work, when i refresh multiple times only 1 request is made to my API
oh I'm sry
I thought you wanted it to refetch every minute
on the same load
not cache it for multiple refreshes
Spectacled CaimanOP
ahhh
yeah
that's why I suggested setInterval
if it works, it works I guess
nice job
I would just use render ? Content : Skeleton
instead of s.loading or whatever
@Jesse I would just use render ? Content : Skeleton
Spectacled CaimanOP
ahh it's because my conditional statement was wrong, i was checking if the length was not 0 then displaying loader
thank you for your help !
@Jesse not cache it for multiple refreshes
Spectacled CaimanOP
do you know if the cached data files with automatically delete or will they remain infinitely? only because i make requests based on user inputs and ive got revalidation on those for when they type the same query within the minute however i dont want to pile up a load of files from hundreds of people querying
they're deleted after a minute
it shouldn't really be a problem