Next.js Discord

Discord Forum

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
Open in Discord
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.
View full answer

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.
The only way to trigger a refresh from the server is to call revalidatePath() or revalidateTag() inside your Server Actions or Route Handlers
Transvaal lionOP
if I dont use "use server" i cant use import { revalidateTag } from 'next/cache'; .
But you’re gonna call revalidateTag/revalidatePathinside 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 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.
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.
@Transvaal lion yeah. does the validateTag implementation right?
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.
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?
@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 revalidateTag()
@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
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
Transvaal lionOP
ok i am trying to figure out wheres the solution..
oh found it