emilkowalski / sonner

An opinionated toast component for React.
https://sonner.emilkowal.ski
MIT License
7.69k stars 237 forks source link

feature: add way to show error in promise toast with server actions #450

Open Bazyli12 opened 2 weeks ago

Bazyli12 commented 2 weeks ago

Describe the feature / bug 📝:

it is posible to handle errror in toast.promise in other way than throwing new error [throw new Error("MESSAGE")] in async function?

i have toast with promise function where i am deleting user, if i have error in server function i wanna have error variant of toast, it is possible with throwing error but it also give it in console etc.

function in client component

onClick={async () => {
    toast.promise(
        deleteUser({
            email: row.getValue("email"),
        }),
        {
            loading: "Usuwanie...",
            success: ({ data }) => data,
            error: ({ message }) => message,
            finally: () => {
                // final
            },
        }
    );
}}

server action function

"use server";

import prisma from "@/lib/prisma";
import { z } from "zod";
import { auth } from "@/lib/auth";
import { revalidatePath } from "next/cache";

const deleteUserSchema = z.object({
    email: z.string().email(),
});

export async function deleteUser(values: z.infer<typeof deleteUserSchema>) {
    const schema = deleteUserSchema.safeParse(values);
    const { user, session } = await auth();

    if (!user) {
        throw new Error("Musisz być zalogowany");
    }

    if (user?.email === values.email) {
        // it works but it also throw error in console and i dont want it...
        throw new Error("Nie możesz usunąć swojego konta");
    }

    if (!schema.success) {
        return { success: true, data: schema.data };
    }
    const { email } = schema.data;

    const deletedUser = await prisma.user.delete({
        where: {
            email,
        },
    });

    if (!deletedUser) {
        throw new Error("Taki użytkownik nie istnieje");
    }

    revalidatePath("/(dashboard)/dashboard/members");

    return {
        success: true,
        data: `Użytkownik ${email} został usunięty`,
    };
}

stupid sollution


// client
onClick={async () => {
    setLoading(true);

    const toastTest = toast;

    toastTest.loading("Usuwanie...");

    const { success, data } = await deleteUser({
        email: row.getValue("email"),
    });

    toastTest.dismiss();

    if (success) {
        toastTest.success(data);
    } else {
        toastTest.error(data);
    }
}}

// server
if (user?.email === values.email) {
    return { success: false, data: "Nie możesz usunąć swojego konta" };
}

it works but it pretty goofy

it is the best solution but also very goofy and long...


// client
onClick={async () => {
    async function generatePromise<T>(
        promise: Promise<{ success: boolean; data: T }>
    ): Promise<T> {
        const action = await promise;
        if (action.success) {
            return action.data;
        } else {
            throw action.data;
        }
    }

    const promise = generatePromise(deleteUser({ email: row.getValue("email") }));

    toast.promise(promise, {
        loading: "Usuwanie...",
        success: (data) => data,
        error: (message) => message,
        finally: () => {
            // final
        },
    });
}}

// server - same as above
if (user?.email === values.email) {
    return { success: false, data: "Nie możesz usunąć swojego konta" };
}
Bazyli12 commented 2 weeks ago

ok after thinking about it, the best way is to add another example in docs with server action... image

but I dont think there is anyone who also uses sonner with server actions in this way xd