Nextjs13 revalidateTag Loading Modal while waiting for revalidation

413 views Asked by At

Got a quick question for you. I'm working on a Next.js frontend app hooked up to a Flask backend. So, I'm triggering a server action when a button is clicked, using the startTransition() function. That action includes a revalidateTag once the data is fetched (I'll share the code below). Now, I'd like to display a Loading modal component. The thing is, the revalidation affects data used in a drawer component, and I want the user to chill while the revalidation is underway – all while keeping them in the loop. To wrap it up, i am looking for a way to await validation. Cheers!

Here i am starting my action, and while pending, my Loading modal is present, but the problem is that it is only present while the action is fetching data, when its done, and revalidation starts, modal is gone and i am not sure how to call it while my revalidation is on going.

 startTransition(() =>
      deleteSources(email, selectedChannelIds)
    );
 {isPending && <LoadingModal />}

Action called

export async function deleteSources(
  email: string,
  channels: WorkspaceChannel[]
) {
  const res = await fetch(
    `https://someroute/test/leave_channels/${email}`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(channels),
    }
  );
  revalidateTag('groups');
}

I have tried everything i found, but i did not found anything relevant.

1

There are 1 answers

0
Jason Bert On

I found a solution in a Next.js GitHub issue that might solve your problem. I've used the hook code below to show a pending state in a component whilst the server action is running.

Action

'use server'

import { revalidateTag } from "next/cache";
import { BLOG_FAVORITES_CACHE_TAG, BLOG_SUMMARIES_CACHE_TAG, deleteBlog, getBlogCacheTag } from "./blogApi";

export async function deleteBlogAction(id: string) {
    const response = await deleteBlog(id);

    if (response.ok) {
        revalidateTag(BLOG_SUMMARIES_CACHE_TAG);
        revalidateTag(BLOG_FAVORITES_CACHE_TAG);
        revalidateTag(getBlogCacheTag(id));

        return {
            success: true,
            message: 'Blog deleted'
        };
    } else {
        return {
            success: false,
            message: 'Blog not deleted'
        };
    }
}

Hook

'use client';

import { useEffect, useRef, useState, useTransition } from "react";

export const useServerAction = <P, R>(
    action: ((_: P) => Promise<R>),
    onFinished?: ((_: R | undefined) => void)
): [(_: P) => Promise<R | undefined>, boolean] => {
    const [isPending, startTransition] = useTransition();
    const [result, setResult] = useState<R>();
    const [finished, setFinished] = useState(false);
    const resolver = useRef<((value?: (R | PromiseLike<R>)) => void)>();

    useEffect(() => {
        if (!finished) return;

        if (onFinished) onFinished(result);
        resolver.current?.(result);

    }, [result, finished, onFinished]);

    const runAction = async (args: P): Promise<R | undefined> => {
        startTransition(() => {
            action(args).then(data => {
                setResult(data);
                setFinished(true);
            });
        });

        return new Promise((resolve) => {
            resolver.current = resolve;
        });
    };

    return [runAction, isPending];
};

Component

'use client';

import { PrimaryButton } from "~f/framework/button";
import { useServerAction } from "~f/web/serverActionClient";
import { deleteBlogAction } from "./blogAction";

export default function BlogDeleteButton({ blogId }: { blogId: string }) {
    const [runAciton, isPending] = useServerAction(deleteBlogAction);

    async function deleteBlog() {
        await runAciton(blogId);
    };

    return (
        <PrimaryButton onClick={deleteBlog} style={{ minWidth: 50 }}>{isPending ? '⌛' : '✖️'}</PrimaryButton>
    )
}

Sources:

https://github.com/vercel/next.js/discussions/51371#discussioncomment-7143482 https://github.com/scorado-ltd/scorado-examples-nextjs-cosmos