Next.js Discord

Discord Forum

React.cache vs unstable_cache?

Answered
Birman posted this in #help-forum
Open in Discord
BirmanOP
I was previously using React.cache for a lot of one-time serverside actions but realized that I want to revalidate them later. I came across next's unstable_cache and it seems perfect.
But now I'm wondering why next's cache is unstable when React also offers a "stable" cache of its own? Are there any fundamental differences between the two
Answered by Jboncz
Nextjs cache deduplicates by persisting the response on disk or in memory, where as react cache deduplicates the request... if that makes sense.
View full answer

230 Replies

@Birman I was previously using React.cache for a lot of one-time serverside actions but realized that I want to revalidate them later. I came across next's unstable_cache and it seems perfect. But now I'm wondering why next's cache is unstable when React also offers a "stable" cache of its own? Are there any fundamental differences between the two
They both of their pros and cons.... unstable is a misnomer, its labeled as unstable as its not finalized and is subject to change, BUT
Warning: This API is unstable and may change in the future. We will provide migration documentation and codemods, if needed, as this API stabilizes.


Its not unstable as in, its buggy, I havent run into any real bugs with it.
Nextjs cache deduplicates by persisting the response on disk or in memory, where as react cache deduplicates the request... if that makes sense.
Answer
@riský wraps his expensive fetches in unstable cache and react cache to get best of both worlds 😄
is that correct
Correct, you typically set a time limit, and gets reset after that time limit. So me and you visitng page x thats using unstable cache get the same data till we hit the page/function after the revalidation time has passed
export async function eventManagementGetRecentTickets() {
    const sqlPool = await sql.connect(eventmgmt_config);
    const sqlRequest = new sql.Request(sqlPool);

    const sqlQuery = `SELECT TOP (300) * FROM [IAMEM].[dbo].[iamevents] WHERE [status] like '%Ticket%' Order by [eId] desc`;

    let queryResult = await sqlRequest.query(sqlQuery);
    await sqlPool.close();

    if (queryResult.recordset.length > 0) {
        return queryResult;
    }

    return null;
}


export default async function Page() {
    const tickets = await unstable_cache(eventManagementGetRecentTickets, [], { revalidate: 60, })()


    return (
        <div className="m-2 bg-paper rounded-md">
            <div className='p-2'>
                {tickets &&
                    <GenericTableV2 data={tickets} />
                }
            </div>
        </div>
    )
};


Heres a simple example.
If me and you go to the page, we are getting the data from the web server not the sql database, once the data is 60 seconds old... the next time someone hits the page or that function gets called, it will fetch new data and store it in memory
@Jboncz <@657067112434499595> wraps his expensive fetches in unstable cache and react cache to get best of both worlds 😄
BirmanOP
is there an example of this? I kinda want to use per-request cache with a revalidateTag option
or is that not possible
tysm btw
that was the solution
Yeah lemme go look at his repo again
You can force unstable cache to revalidate by passing in a new keyParts
Unstable cache is pretty magical.
tbh
😄
revalidate: The number of seconds after which the cache should be revalidated. Omit or pass false to cache indefinitely or until matching revalidateTag() or revalidatePath() methods are called.
BirmanOP
ok so I'm getting some strange behaviour
where functions cached with React.cache are reset after using revalidateTag
i want to have some functions that are cached per request... but revalidateTag revalidates all of them it seems?
React.cache is only for one request/render
unstable_cache is persistent across requests
BirmanOP
yeah i learnt about that
React.cache is being refreshed when I call revalidateTag in one of my server actions though
is that normal behaviour
i cant remember what react cache for client side does tho, but considineg revalidateTag on server action does lots, i assume it clears
BirmanOP
i have a react cache on serverside
it's being cleared even though it's the same request, i just called revalidateTag and all the react cache functions ran again
@Birman it's being cleared even though it's the same request, i just called revalidateTag and all the react cache functions ran again
Havana
The thing with react cache is that it doesn't persist forever, it has a lifecycle, and whenever you refresh the page it will clear that specific cache out
BirmanOP
yes that's what I want
for certain functions
Havana
okay, so whats the issue 😄
BirmanOP
calling revalidateTag clears that specific cache out
on a server action
somehow
Havana
That is something unusual, it only needs to work for the unstable cache
There have been reports and discussions about revalidateTag and revalidatePath unintentionally clearing or affecting caches beyond their intended scope, including those related to React.cache.
Havana
Why do you even need to revalidate it anyways?
If you know that this will clear out on refresh anyways
@Havana Why do you even need to revalidate it anyways?
BirmanOP
i have some functions that need to be cached per request, and some functions that need to be cached for all requests
Havana
yeah, so divide them by react cache and unstable cache
😄
Or you are saying that revalidating the unstable cache also revalidates the react one
Havana
😂
wow, that is actually crazy
BirmanOP
😭
so what should I do
this is sad
i will just stop using unstable cache and force the user to completely refresh the page for updated data
ok tbh, I don't know where and how the react cache is being cleared, but I just know that the 2 don't work in harmony right now
it was working before I used unstable cache
@Birman it was working before I used unstable cache
Havana
maybe that causes the issue
I never faced things like that in my life
You know, what if you scope out the react cache?
Make it work on the client side, and the unstable cache leave on the server side
It can work
BirmanOP
bro wait
this is actually the weirdest behaviour
i've ever seen
@Birman i've ever seen
Havana
Try to scope it out, should work
BirmanOP
no like
the function in react.cache runs twice back-to-back
first weird behaviour
Havana
Yep
BirmanOP
it wasn't happening on any other pages
Havana
But try scoping out, since the revalidation happens on server side, it shouldnt affect the react cache, even if its a bug
Wrap component with this react cache, and in the parent use the unstable cache
The child should be client
BirmanOP
react cache was for server side state
for a request
Havana
Server side state?
or a value
@Havana Server side state?
BirmanOP
like state management
for server side
for 1 request
it's the equivalent of setting an object likereq.customData in any other server framework
Havana
You can try using unstable cache with a unique key, for example Date.now()
It will revalidate on page reload
instead of using react cache
just don't set any revalidate time for it
BirmanOP
i need it as per-request
and there isn't any reliable id
i can provide
i'll have to pass something down somehow to other pages
Havana
Since its a cache that will be cleared out on a refresh you can just create any unique key to it, doesn't matter, even crypto.randomUUID()
across different pages
from the same request
Havana
But you said only this page doesn't work with the react cache
the others you can leave as is
the unstable cache, the one you actually store and revalidate when needed, you don't touch
You only replace the react cache, the one time cache, with another unstable cache
@Havana But you said only this page doesn't work with the react cache
BirmanOP
it's behaving strangely
and I can't even figure out how
Havana
Maybe its not about react cache, yeah 😄
maybe some of the page's code is making it do weird things
BirmanOP
like is it because i'm using <Link>'s to navigate
Havana
ohhhhh
pass prefetch = {false} to the link
prop
and tell me if it solved the issue
BirmanOP
why would prefetch resolve the issue
it only prefetches once
Havana
Try that
BirmanOP
prefetch is false in dev mode
by default
so it cannot be the issue
Havana
Oh, in dev
okay, nvm
BirmanOP
im thinking maybe it's because of the page
do rscs run once?
like
Havana
Yes? 😄
BirmanOP
if you do <Link> navigate to a different page, and <Link> to navigate back
what does the rsc do
for that page
does it re-run?
but that wouldn't make a lot of sense either
Havana
It doesn't
BirmanOP
tbh i can completely stop using react.cache
if there was a way to store a javascript object on the singular request object
Havana
this happens when you load the page, or go back after you clicked the link?
@Havana this happens when you load the page, or go back after you clicked the link?
BirmanOP
yes, and it happens randomly
sometimes it the cache dies
sometimes the cache works
all im doing is navigating back and forth
Havana
And it doesn't work because you use revalidate stuff inside that same page?
BirmanOP
i commented out the revalidate stuff
and somehow... still doesn't work?
i commented out all the imports frmo next/cache
in my entire app
and I can assure u the cached function runs on the server
Havana
Well, I just read that when navigating between pages, using Link, it doesn't clear the cache out
The react cache
BirmanOP
it does!
Havana
Well this is crazy
BirmanOP
but i thought it's a single request
since <Link>'s are just doing window.history.pushState
Havana
it is
When navigating it's not supposed to clear this out
BirmanOP
wait are there extra requests to rscs when navigating to a new page?
that would... make sense? if it's serverside dynamic data
and react cache is per request... so that's technically a new instance
Havana
yes, there are
BirmanOP
ahhh
i think i kind of undestand
what im doing wrong
Havana
well if for example the server page requires a fresh data, if its not cached or static or etc, it refetches it
BirmanOP
it's a bit weird though
Havana
But in your case it is cached...
BirmanOP
OOOOOHHHH
i have fresh data + cached data
that's why the cached function is running again too
Havana
well, thats why
BirmanOP
yeahhh
Havana
Now you have to take care of it
BirmanOP
ok
Havana
Gl with that
BirmanOP
ill try to figure something out 😭
Havana
Trust me, its the easier part 😄
the hardest was to figure out why
BirmanOP
is there a way to send a prop to a rsc when data is refetched (from the client)
Havana
No, you can't wrap OR import rsc from client
You have to do the opposite
Or do it all SSR
And then pass the data to client if needed
BirmanOP
like there's a lot of ?_rsc=.... requests
is it possible to configure some kind of data to be passed
Havana
wdym, like whitelisting data?
BirmanOP
nvm
i haven't slept in like 30 hours
Havana
😄
BirmanOP
i think it's about time
Havana
Glad we figured out the issue though
BirmanOP
yeah, tbh i didn't fully understand rsc
@Birman yeah, tbh i didn't fully understand rsc
Havana
will help where I can
BirmanOP
yee tysm
ohhhhh i know how to solve my state issue
is there a way to cache a function that's dependent on a serverside cookie
as long as the cookie does not change
the function returns the same result
Havana
yes
you can access cookies from serverside using cookies import
Havana
obv using unstable cache
yes
BirmanOP
ok
so like
React.cache(() => {
  /* cookie */
  return unstable_cache(
    () => {}
    [cookie]
  )();
});
smth like that
should work
Havana
why double cache?
only unstable
return unstable_cache(() => function(), [unique-cookie], {
revalidate: 60 * 60 * 20,
tags: ["tags you want(not required)"],
});
something like this
@Havana why double cache?
BirmanOP
idk i just like caching
Havana
😄
it's dynamic
Havana
import { cookies } from "next/headers";
then cookies().get("key")?.value
BirmanOP
so like
// cached_functions.ts
const getData = async () => {
  const cookieList = cookies();
  return unstable_cache(
    () => function(),
    [cookieList.get("key")?.value]
  )();
}
Havana
yeah, but get the value before the caching
just a nice practice
BirmanOP
wdym get the value before caching
Havana
const cookieValue = cookieList.get("key")?.value
BirmanOP
ah yeah
Havana
😄
thats the way
BirmanOP
i am just typing it out
yeah
ok
Havana
ye
You will also need to make sure if the cookie value exists, if not set the key to 'default'
const cookieValue = cookieList.get("key")?.value || "default";
BirmanOP
i got a middleware to make sure that cookie exists
it's chill
Havana
oh, so user doesn't even get on the page if he doesn't have the cookie?
Good one
lol i come back here with so many messages and you have worked out issue right?
Havana
yep
@riský lol i come back here with so many messages and you have worked out issue right?
BirmanOP
yeah i just forgot rsc is refetched if there's dynamic data
and the cached functions rerun
beacuse it's no longer the same request