How do I refresh the related UI after passing data into a server component in Next.js 15?
Answered
Transvaal lion posted this in #help-forum
Transvaal lionOP
// This is the client component
'use client';
import { updateText } from './parent_comp'; // Import updateText
export default function ClientComp() {
const handleClick = async () => {
await updateText('devtz007'); // Update the text
};
return (
<button
onClick={handleClick}
style={{ color: 'black', background: 'coral' }}
>
Send Text
</button>
);
}
// Server Component
'use server'; // Server Action
import ClientComp from './client_comp';
import { revalidatePath } from 'next/cache';
// Initial text variable (you can fetch or compute this)
let text = 'initial text'; // Initial text
// Server action to update the text
export async function updateText(newText: string): Promise<string> {
text = newText; // Update the text
//revalidatePath('/'); // Revalidate the path to refresh the server-side content
return text; // Return the updated text
}
// Server Component
export default async function ParentComp() {
console.log(text); // Log the updated text
return (
<>
<p style={{ color: 'green', backgroundColor: 'coral' }}>
Received Text: {text}
</p>
<ClientComp />
</>
);
}
I am calling the
updateText
function from the server component and passing true
into it. It is successfully reaching the server component, but the effect doesn't show immediately without reloading. The effect should be displayed as soon as the text is updated from the client component. I don't want to use
router.refresh()
and revalidatePath('/');
because it will re render full page.Answered by LuisLl
Yes, this bit is. To pass a tag to a
fetch()
you need to do it this way, same would apply for unstable_cache()
but when you pass the tag to your revalidateTag()
call, passing the whole options object {next: tags: [“aaa”]}
seems over complicated since you only need the “aaa” bit.31 Replies
First, don’t mark server components with “use server”. (Your code snippet seems to indicate you’re marking the Server Component with it)
Components are Server Components by default, what “use server” does is exposing async functions as POST endpoints to be reachable by the client. Don’t do this, “use server” and “use client” aren’t opposite to each other.
Components are Server Components by default, what “use server” does is exposing async functions as POST endpoints to be reachable by the client. Don’t do this, “use server” and “use client” aren’t opposite to each other.
The only way to trigger a refresh from the server is to call
revalidatePath()
or revalidateTag()
inside your Server Actions or Route HandlersTransvaal lionOP
if I dont use "use server" i cant use
import { revalidateTag } from 'next/cache';
.But you’re gonna call
revalidateTag/revalidatePath
inside the server action, and that file will be marked with “use server”
@Transvaal lion if I dont use "use server" i cant use `import { revalidateTag } from 'next/cache';
` .
Also… are you using both the pages/ and app/ routers?
@LuisLl Also… are you using both the pages/ and app/ routers?
Transvaal lionOP
app route only in nextjs 15
I asked because the error message says something about importing a component that uses "revalidateTag" inside the pages/ folder
@LuisLl But you’re gonna call `revalidateTag/revalidatePath`inside the server action, and that file will be marked with `“use server”`
Transvaal lionOP
i am not sure about the pages directory in that error too
'use client';
import { updateText } from './parent_comp';
export default function ClientComp() {
const handleClick = async () => {
await updateText('devtz007', { next: { tags: ['textUpdate'] } }); // Pass cache tag
};
return (
<button
onClick={handleClick}
style={{ color: 'black', background: 'coral' }}
>
Send Text
</button>
);
}
and 'use server';
import ClientComp from './client_comp';
import { revalidateTag } from 'next/cache';
let text = 'initial text'; // This would be in a DB in production
// ✅ Server action to update text with cache tag revalidation
export async function updateText(
newText: string,
options: { next: { tags: string[] } },
) {
if (options.next.tags.includes('textUpdate')) {
// Perform the text update only if the 'textUpdate' tag is present
text = newText; // Update the text
revalidateTag('textUpdate'); // Revalidate the correct tag
}
}
// ✅ Server Component (Automatically updates when `textUpdate` tag is revalidated)
export default async function ParentComp() {
return (
<>
<p style={{ color: 'green', backgroundColor: 'coral' }}>{text}</p>
<p>abcd</p>
<ClientComp />
</>
);
}
. i used revalidateTag() and it works with immediate effect. but I dont quite sure if it really revalidating only for related things. also i have noticed if i move ClientComp somewhere else and clicked the button there, in parent comp effect doesnt apply immediately.@LuisLl The only way to trigger a *refresh* from the server is to call `revalidatePath()` or `revalidateTag()` inside your Server Actions or Route Handlers
revalidateTag()
triggers the fetch to run again and fetch fresh data, after that, the new React Server Component payload coming from the Server Action reconciliates with the existing Virtual DOM on the browser to only update the necessary DOM nodes and preserve state.@Transvaal lion 'use client';
import { updateText } from './parent_comp';
export default function ClientComp() {
const handleClick = async () => {
await updateText('devtz007', { next: { tags: ['textUpdate'] } }); // Pass cache tag
};
return (
<button
onClick={handleClick}
style={{ color: 'black', background: 'coral' }}
>
Send Text
</button>
);
}
and 'use server';
import ClientComp from './client_comp';
import { revalidateTag } from 'next/cache';
let text = 'initial text'; // This would be in a DB in production
// ✅ Server action to update text with cache tag revalidation
export async function updateText(
newText: string,
options: { next: { tags: string[] } },
) {
if (options.next.tags.includes('textUpdate')) {
// Perform the text update only if the 'textUpdate' tag is present
text = newText; // Update the text
revalidateTag('textUpdate'); // Revalidate the correct tag
}
}
// ✅ Server Component (Automatically updates when `textUpdate` tag is revalidated)
export default async function ParentComp() {
return (
<>
<p style={{ color: 'green', backgroundColor: 'coral' }}>{text}</p>
<p>abcd</p>
<ClientComp />
</>
);
}
. i used revalidateTag() and it works with immediate effect. but I dont quite sure if it really revalidating only for related things. also i have noticed if i move ClientComp somewhere else and clicked the button there, in parent comp effect doesnt apply immediately.
also i have noticed if i move ClientComp somewhere else and clicked the button there, in parent comp effect doesnt apply immediately.
Not sure I understand what you're saying but.. this is probably because the data is invalidated, which means it needs to be fetched again but you need to trigger it. Try refreshing the page and see if you load the latest data.
@Transvaal lion 'use client';
import { updateText } from './parent_comp';
export default function ClientComp() {
const handleClick = async () => {
await updateText('devtz007', { next: { tags: ['textUpdate'] } }); // Pass cache tag
};
return (
<button
onClick={handleClick}
style={{ color: 'black', background: 'coral' }}
>
Send Text
</button>
);
}
and 'use server';
import ClientComp from './client_comp';
import { revalidateTag } from 'next/cache';
let text = 'initial text'; // This would be in a DB in production
// ✅ Server action to update text with cache tag revalidation
export async function updateText(
newText: string,
options: { next: { tags: string[] } },
) {
if (options.next.tags.includes('textUpdate')) {
// Perform the text update only if the 'textUpdate' tag is present
text = newText; // Update the text
revalidateTag('textUpdate'); // Revalidate the correct tag
}
}
// ✅ Server Component (Automatically updates when `textUpdate` tag is revalidated)
export default async function ParentComp() {
return (
<>
<p style={{ color: 'green', backgroundColor: 'coral' }}>{text}</p>
<p>abcd</p>
<ClientComp />
</>
);
}
. i used revalidateTag() and it works with immediate effect. but I dont quite sure if it really revalidating only for related things. also i have noticed if i move ClientComp somewhere else and clicked the button there, in parent comp effect doesnt apply immediately.
Also, if both of your server action
Only server actions should be exported from files marked with
updateText()
and your server component ParentComp()
are in the same file marked with "use server"
that's a bad practice. Only server actions should be exported from files marked with
"use server"
. You're exposing all exported async functions as POST endpoints.Transvaal lionOP
actions.tsx
'use server';
import { revalidateTag } from 'next/cache';
const textStore = new Map<string, string>();
textStore.set('text', 'initial'); // Default value
export async function updateTextAction(
newText: string,
options: { next: { tags: string[] } },
) {
if (options.next.tags.includes('aaa')) {
textStore.set('text', newText); // Store updated text
revalidateTag('aaa'); // Revalidate cache
}
}
export default async function getText() {
return textStore.get('text');
}
, client_comp.tsx 'use client';
import { updateTextAction } from './actions';
export default function ClientUpdater() {
const handleUpdate = async () => {
await updateTextAction('devtz007', { next: { tags: ['aaa'] } });
};
return (
<div>
<button onClick={handleUpdate}>Update</button>
</div>
);
}
, text_show.tsx export default async function ServerComp(prop) {
console.log(prop.prop);
return (
<div>
<div>Received Text: {prop.prop}</div>
</div>
);
}
, page.tsx /* Import components */
import getText from '@/learning_playground/async/actions';
import ServerComp from '@/learning_playground/async/text_show';
import ClientComp from '@/learning_playground/async/client_comp';
import './page.scss';
const ExLayout = async () => {
const text = await getText();
console.log(text);
return (
<div className="exLayout">
<div>Page</div>
<ServerComp prop={text} />
<ClientComp />
</div>
);
};
ExLayout.displayName = 'ExLayout';
export default ExLayout;
. this is my setup . is this setup okay? I have made action server comp as your recommendation. I have placed revalidatetag
on the actions.tsx component. is this ok?Is everything working as you expected now? If so, all looks good to me except one little detail I’ve mentioned before.
Prefer not to fetch data with Server Actions, instead, move that fetching logic to a different file not marked with “use server” and call it directly on your Server Component.
Prefer not to fetch data with Server Actions, instead, move that fetching logic to a different file not marked with “use server” and call it directly on your Server Component.
To fetch data, always prefer to do it directly inside of Server Components, which means you can inline the data fetching logic inside the Server Component body, or make a little utility function outside of the
“use server”
scope, and call this “regular” async function inside of your component (my preferred approach).@Transvaal lion actions.tsx 'use server';
import { revalidateTag } from 'next/cache';
const textStore = new Map<string, string>();
textStore.set('text', 'initial'); // Default value
export async function updateTextAction(
newText: string,
options: { next: { tags: string[] } },
) {
if (options.next.tags.includes('aaa')) {
textStore.set('text', newText); // Store updated text
revalidateTag('aaa'); // Revalidate cache
}
}
export default async function getText() {
return textStore.get('text');
}
, client_comp.tsx 'use client';
import { updateTextAction } from './actions';
export default function ClientUpdater() {
const handleUpdate = async () => {
await updateTextAction('devtz007', { next: { tags: ['aaa'] } });
};
return (
<div>
<button onClick={handleUpdate}>Update</button>
</div>
);
}
, text_show.tsx export default async function ServerComp(prop) {
console.log(prop.prop);
return (
<div>
<div>Received Text: {prop.prop}</div>
</div>
);
}
, page.tsx /* Import components */
import getText from '@/learning_playground/async/actions';
import ServerComp from '@/learning_playground/async/text_show';
import ClientComp from '@/learning_playground/async/client_comp';
import './page.scss';
const ExLayout = async () => {
const text = await getText();
console.log(text);
return (
<div className="exLayout">
<div>Page</div>
<ServerComp prop={text} />
<ClientComp />
</div>
);
};
ExLayout.displayName = 'ExLayout';
export default ExLayout;
. this is my setup . is this setup okay? I have made action server comp as your recommendation. I have placed `revalidatetag` on the actions.tsx component. is this ok?
Minor details:
- the actions file doesn’t need the .tsx extension, just .ts is fine
- prefer declaring components as function statements so you don’t need to manually specify a DisplayName.
- the actions file doesn’t need the .tsx extension, just .ts is fine
- prefer declaring components as function statements so you don’t need to manually specify a DisplayName.
@LuisLl Minor details:
- the actions file doesn’t need the .tsx extension, just **.ts** is fine
- prefer declaring components as function statements so you don’t need to manually specify a *DisplayName*.
Transvaal lionOP
yeah. does the validateTag implementation right?
@Transvaal lion yeah. does the validateTag implementation right?
Looks good, passing the whole tag object to the action seems a little unnecessary tho.
Just pass the tag, you know it beforehand. Same inside the server action, just call
await updateTextAction( 'devtz007',
{ next: { tags: ['aaa'] } } );
Just pass the tag, you know it beforehand. Same inside the server action, just call
revalidateTag()
with the “aaa” tag.I assume you tried running your app and it works?
@LuisLl I assume you tried running your app and it works?
Transvaal lionOP
yeah it works fine. But what happen if I implement same things in another page?
@LuisLl Looks good, passing the whole tag object to the action seems a little unnecessary tho.
`await updateTextAction( 'devtz007',
{ next: { tags: ['aaa'] } } );`
Just pass the tag, you know it beforehand. Same inside the server action, just call `revalidateTag()` with the “aaa” tag.
Transvaal lionOP
in their documentation they use like this .
@Transvaal lion in their documentation they use like this .
I mean the way you work with tags is apart from the implementation, you can continue to do it your way.
Not sure I follow you here, but what that’s doing is tagging a fetch request to open the possibility that you invalidate directly that fetch call via
Not sure I follow you here, but what that’s doing is tagging a fetch request to open the possibility that you invalidate directly that fetch call via
revalidateTag()
@LuisLl I mean the way you work with tags is apart from the implementation, you can continue to do it your way.
Not sure I follow you here, but what that’s doing is tagging a fetch request to open the possibility that you invalidate directly that fetch call via `revalidateTag()`
Transvaal lionOP
thats nextjs documentation
@Transvaal lion in their documentation they use like this .
Yes, this bit is. To pass a tag to a
fetch()
you need to do it this way, same would apply for unstable_cache()
but when you pass the tag to your revalidateTag()
call, passing the whole options object {next: tags: [“aaa”]}
seems over complicated since you only need the “aaa” bit.Answer
@LuisLl Yes, this bit is. To pass a tag to a `fetch()` you need to do it this way, same would apply for `unstable_cache()` but when you pass the tag to your `revalidateTag()` call, passing the whole options object `{next: tags: [“aaa”]}` seems over complicated since you only need the “aaa” bit.
Transvaal lionOP
ok. thank you so much for you help. 🥰
Glad I could help;), mark the solution for the original question!
@LuisLl Glad I could help;), mark the solution for the original question!
Transvaal lionOP
how to mark?
@Transvaal lion how to mark?
Right click > Apps > Mark solution
Or on phone hold on message > Apps > Mark solution
Or on phone hold on message > Apps > Mark solution
Transvaal lionOP
ok i am trying to figure out wheres the solution..
oh found it