Fetching data in a modal and revalidating it when crud is performed
Answered
Plott Hound posted this in #help-forum
Plott HoundOP
Say you have a simple task model: ID, name, description etc, you want to display a list of the task names on the main page, and when you click one it opens a modal showing all the info and lets you perform crud on the details. so you fetch the simple task view just including ID and name in your server component and map through them in your main page. Since you don't want a modal element for every item you make a single modal component outside of the map, when the user clicks the task item you set an active task id state so you can pass the id into the modal component, the modal component then uses this ID to perform a fetch using a server action and return the data in the modal.
This works great and keeps the page load fast since we don't need to fetch all of the task data on page load. the alternative would be to fetch all of the task data and just pass the active task data to the modal but imo this is less efficient and scalable.
What i'd like advice on is how to correctly reflect changes in the task data modal when CRUD is performed on the data.
Here's what i'm doing currently: when i perform crud i also use revalidatePath which successfully re-triggers the fetch (from prisma in this case). it successfully fetches the simple tasks and they are reflected in the main page.
Because the modal component is only being passed a task ID it doesn't know that any data has changed so it doesn't trigger the server action to fetch again so the data in the modal does not update until we close and reopen the modal.
I have no idea how i can make the expanded task data from the server action fetch or invalidate again. maybe using revalidateTag on the prisma fetch which im going to try now.
maybe my whole approach is wrong but any ideas on how to implement this in a next way would be greatly appreciated.
relevant stack: next14, prisma
This works great and keeps the page load fast since we don't need to fetch all of the task data on page load. the alternative would be to fetch all of the task data and just pass the active task data to the modal but imo this is less efficient and scalable.
What i'd like advice on is how to correctly reflect changes in the task data modal when CRUD is performed on the data.
Here's what i'm doing currently: when i perform crud i also use revalidatePath which successfully re-triggers the fetch (from prisma in this case). it successfully fetches the simple tasks and they are reflected in the main page.
Because the modal component is only being passed a task ID it doesn't know that any data has changed so it doesn't trigger the server action to fetch again so the data in the modal does not update until we close and reopen the modal.
I have no idea how i can make the expanded task data from the server action fetch or invalidate again. maybe using revalidateTag on the prisma fetch which im going to try now.
maybe my whole approach is wrong but any ideas on how to implement this in a next way would be greatly appreciated.
relevant stack: next14, prisma
Answered by Ray
use intercepting routes to create the modal, so you could fetch the data server side instead of using server action
https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes#modals
https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes#modals
47 Replies
use intercepting routes to create the modal, so you could fetch the data server side instead of using server action
https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes#modals
https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes#modals
Answer
@Ray use intercepting routes to create the modal, so you could fetch the data server side instead of using server action
https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes#modals
Plott HoundOP
Thanks. i was looking into this and it was my initial approach, but because the tasks don't need their own url and i would like to avoid users even sharing/accessing those links, is there a way i can do this without the url changing? I'll happily make a dynamic task route but im not sure i could use those in a modal without intercepting routes and the url changing.
failing all that, would it be acceptable practice to do as you suggest but setup a redirect to the task list page from the dedicated task urls? or would that then break the modals. sorry for all the questions!
failing all that, would it be acceptable practice to do as you suggest but setup a redirect to the task list page from the dedicated task urls? or would that then break the modals. sorry for all the questions!
oh then render the list alone with the modal
or use client side fetching with swr or react-query to invalidate after mutation
Plott HoundOP
Thanks @Ray. After some thought i decided that you're original idea of intercepting routes is the way to go.
if my task path is:
would the intercept route be:
also do i need a default.tsx file with
if so which folder should it go in?
thank you
if my task path is:
app/(board-layout)/task/[id]/page.tsxwould the intercept route be:
app/(board-layout)/@modal/(.)task/[id]/page.tsx?also do i need a default.tsx file with
export default function Default() {
return null
}if so which folder should it go in?
thank you
yes
default.tsx should be under @modal
default.tsx should be under @modal
app/(board-layout)/@modal/default.tsx
@Ray app/(board-layout)/@modal/default.tsx
Plott HoundOP
Thanks i got it working. Really sorry but i have one final question.
my main page has a link that navigates to the route:
I have the single task pages working here
and i have this intercepting route:
currently when i open the modal it just shows the contents of
when i refresh the page or go to the link directly (/task/some-id) it shows the actual task data. how can i make it so opening the modal shows the content of this page and not "Hello! this is a modal" ?
my main page has a link that navigates to the route:
<Link href={`/task/${task.id}`}>I have the single task pages working here
app/(board-layout)/task/[id]/page.tsxand i have this intercepting route:
app/(board-layout)/@modal/(.)task/[id]/page.tsxcurrently when i open the modal it just shows the contents of
app/(board-layout)/@modal/(.)task/[id]/page.tsx which is just this at the moment:const page: FC<pageProps> = ({}) => {
return (
<Modal>
Hello! this is a modal
</Modal>
)
}
export default pagewhen i refresh the page or go to the link directly (/task/some-id) it shows the actual task data. how can i make it so opening the modal shows the content of this page and not "Hello! this is a modal" ?
both of the file need to be same content except the modal wrapper
Plott HoundOP
i was looking at this example: https://github.com/vercel-labs/nextgram/blob/main/src/app/%40modal/(.)photos/%5Bid%5D/page.tsx and it seems like its the same but has a wrapper
Plott HoundOP
@Ray i wish i could buy you a beer mate, thank you so much!
absolute lifesaver
lol i would love it
Plott HoundOP
seriously appreciated. thank you and have a great day
np
@Ray np
Plott HoundOP
ur gonna hate me but i have one final question if you have a minute. when im performing CRUD in board or the task page it updates fine when i use:
but it doesn't refetch while the modal is open. is this even possible and if so what path would i need to revalidate? or is this one of those cases where revalidateTag might be better
revalidatePath(`/board/${parse.data.boardId}`);
revalidatePath(`/task/${parse.data.taskId}`);but it doesn't refetch while the modal is open. is this even possible and if so what path would i need to revalidate? or is this one of those cases where revalidateTag might be better
I thought about trying the intercept route too:
app/(board-layout)/@modal/(.)task/[id]/page.tsx but im not sure this is even a path or what the path for this would look like in revalidatePathyes try it
revalidatePath('app/(board-layout)/@modal/(.)task/[id]', 'page')@Ray `revalidatePath('app/(board-layout)/@modal/(.)task/[id]', 'page')`
Plott HoundOP
thanks i tried a few combinations and nothiing worked, then i tried
revalidatePath('/', 'layout') and sure enough it also didnt update the data. i'm going to do some more investigating in my project but thank you for showing me the right way to do ithmm let me test
Plott HoundOP
im going to double check its not my code
there is an issue about this
revalidateTag doesn't work too
just tried with 14.0.5-canary.7, it will cause infinity fetch lol
https://github.com/vercel/next.js/issues/54173#issuecomment-1847825539
this workaround works
this workaround works
keep an eyes to the issue and hope it will fix soon 😆
@Ray keep an eyes to the issue and hope it will fix soon 😆
Plott HoundOP
thanks! im glad i found out now and not after days of testing so thank you
i'll try this workaround tomorrow and see how it goes
or this can be a V2 feature i come back to in a few months 😄
"use client";
import { update } from "@/app/action";
import { usePathname, useRouter } from "next/navigation";
export function Form() {
const router = useRouter();
const pathname = usePathname();
return (
<form
onSubmit={async (e) => {
e.preventDefault();
await update(new FormData(e.currentTarget));
router.push(`${pathname}?ts=${Date.now()}`);
}}
>
<input type="text" name="name" />
<button>submit</button>
</form>
);
}this is what i make it works
@Ray this is what i make it works
Plott HoundOP
Thanks! I had a mess around for a few hours today and this workaround works, but only when i remove any revalidatePath, so while it updates in the modal, when i close the modal nothing else is updated. I'm gonna fork this and come back to it when its fixed. thanks again and really appreciate your help
@Plott Hound Thanks! I had a mess around for a few hours today and this workaround works, but only when i remove any revalidatePath, so while it updates in the modal, when i close the modal nothing else is updated. I'm gonna fork this and come back to it when its fixed. thanks again and really appreciate your help
https://github.com/vercel/next.js/pull/59585
the fix has been merged
the fix has been merged
@Ray https://github.com/vercel/next.js/pull/59585
the fix has been merged
Plott HoundOP
Oh nice! Thanks
Plott HoundOP
@Ray just an update: I just tried the canary and it seems like the data still isn't changing when i revalidate the path. also it now throws a new
browser errors:
the errors only throw when i've revalidated a path and then navigate back. I'll wait for release to test it more but thought i'd update you on the progress.
Application error: a client-side exception has occurred (see the browser console for more information). when you navigate back from an open modal.browser errors:
Unhandled Promise Rejection: Error: SEGMENT MISMATCH
Warning: Cannot update a component (`HotReload`) while rendering a different component (`Router`). To locate the bad setState() call inside `Router`
Error: SEGMENT MISMATCH
The above error occurred in the <Router> componentthe errors only throw when i've revalidated a path and then navigate back. I'll wait for release to test it more but thought i'd update you on the progress.
let me know if you get it working on your local test just in case its something in my local code. cheers
@Ray I think the fix does not release in canary yet
Plott HoundOP
ah sorry for bothering you then! i'll give it some time
@Plott Hound ah sorry for bothering you then! i'll give it some time
just tried and it does update the cache correctly with
revalidatePath in 14.0.5-canary.26@Ray just tried and it does update the cache correctly with `revalidatePath` in `14.0.5-canary.26`
Plott HoundOP
This is all I wanted for Xmas XD
Thanks for updating me. I tried canary yesterday and can confirm the cache is updating correctly!!!
I think there are still some issues people are facing but this is great news we’re on the right path (no pun intended) https://github.com/vercel/next.js/issues/54173