Next.js Discord

Discord Forum

revalidating dynamic routes

Answered
Alapaha Blue Blood Bulldog posted this in #help-forum
Open in Discord
Avatar
Alapaha Blue Blood BulldogOP
I have a client component in which I show a list of items.
This is shown in a dynamic route (as you can see from the example below)

// app/lists/[listId]
"use client"

export default function ListPage({
  params,
}: {
  params: { listId: string };
}) {

  const { data, isLoading, isError } = api.item.getAll.useQuery({ listId });

// ..loading and error handling..

  return <div>
           <AddItem/> 
           {data.map((item) => <ItemCard key={item.id} item={item} />}
         </div>

}


the (client component) AddItem has the purpose of adding a new item by calling a server action when submitted:
"use client"

export const AddItem: React.FC<AddItemProps> = ({ listId }) => {
  return <form
        action={async () => {
          await createItem({
            name: "dummyName",
            description:"dummyDescription",
            listId,
          });
        }}
      >...</form>
}


Finally, the createItem server action is as follow:
"use server"

export async function createItem(props: ItemCreateInput) {
  await api.item.create.mutate(props);
  revalidatePath(`/lists/${props.listId}`);
}


By doing this (especially the revalidatePath at the end of the server action), I would expect the UI to match this change immediately, but the changes apply only after reloading the page.
I tried adding a router.refresh() at the end of the async function in the form action prop, but that doesn't change anything.

I also noticed that this bug is present only on dynamic routes: I have basically the same logic in the static route app/lists (where I allow to add a new new list instead), and the UI updates as expected there.

In the revalidatePath docs page, they mention that "with a dynamic route segment [...] The invalidation only happens when the path is next visited", but they don't provide a way to address this scenario in other ways. What should I do here?
Answered by Ray
"use client"

export const AddItem: React.FC<AddItemProps> = ({ listId }) => {
  const utils = api.useUtils();

  return <form
        action={async () => {
          await createItem({
            name: "dummyName",
            description:"dummyDescription",
            listId,
          });
          await utils.item.getAll.invalidate({ listId })
        }}
      >...</form>
}
View full answer

13 Replies

Avatar
Arinji
@Alapaha Blue Blood Bulldog client page might be an issue, are you sure you need "use client"
wait no its cause you are using an external function aka api.item.getAll
wrap it in a unstable_cache, set the tags and the key parts as the listId
Avatar
Alapaha Blue Blood BulldogOP
@Arinji thanks for the answer!
Since I'm using trpc, I could actually have that same query in its "server version" (aka without using a hook), but I'm also using some useState in that page (that I haven't included in the original question for simplicity), so it's not clear to me how can I handle this cache invalidation while keeping this as a client component (because those state are needed).
This is the component with the missing states I'm talking about:

// app/lists/[listId]
"use client"

export default function ListPage({
  params,
}: {
  params: { listId: string };
}) {
  
  const [view, setView] = useState<"card" | "table">("card");
  const [sorting, setSorting] = useState<SortingState>([
    {
      id: "createdAt",
      desc: true,
    },
  ]);

  const { data, isLoading, isError } = api.item.getAll.useQuery({ listId, options: { sorting }, });

// ..loading and error handling..

  return <div>
           <AddItem/> 
           {view == "card" ? 
                data.map((item) => <ItemCard key={item.id} item={item} />
                : <ItemTable
                    data={data}
                    sorting={sorting}
                    setSorting={setSorting}
                  />
           }
         </div>

}
Avatar
Arinji
did you try the unstable cache?
it wraps your api
Avatar
Ray
I think you need to invalidate the cache for the query since you are using client side rendering with react-query
or use the mutation from trpc
Avatar
Ray
"use client"

export const AddItem: React.FC<AddItemProps> = ({ listId }) => {
  const utils = api.useUtils();

  return <form
        action={async () => {
          await createItem({
            name: "dummyName",
            description:"dummyDescription",
            listId,
          });
          await utils.item.getAll.invalidate({ listId })
        }}
      >...</form>
}
Answer
Avatar
Alapaha Blue Blood BulldogOP
@Ray thank you so much, didn't know about the useUtils, working as expected now 🙌🏼
Avatar
Ray
and you may not need revalidatePath there since you are rendering on client side