Next.js Discord

Discord Forum

How can i send like a notification with redirect from a server action

Unanswered
Mawhadmd posted this in #help-forum
Open in Discord
I'm reading the documentation and in the ''mutating data" section i had an idea of showing toast notification to user when they're done editing (redirected back), but the only way i can think of is through searchparams, but this will leave unwanted residue in the url. is there another way to help me achieve this?

23 Replies

For navigation on the client side
Or if you want in the server action you would use the redirect() function i believe
@American Chinchilla Or if you want in the server action you would use the redirect() function i believe
i'm using this, but AFAIK i can only add ?params= to the url, this will distort the url
You can pass a relative URL
Like in their example doc redirect(“/posts”)
So there wont any URL query params
@American Chinchilla So there wont any URL query params
My english is confused, What i was saying is that i want to inform the page that i'm redirecting users to that they've done the action successfully, hence display the notification.
I used query params, but i'm seeking another way
American Chinchilla
Then you would use client side navigation
Using next.js router from next/navigation
@American Chinchilla Then you would use client side navigation
how do i know if the server actions has succeeded?
@Mawhadmd how do i know if the server actions has succeeded?
American Chinchilla
The server actions can return data
Or use the actionFormStatus hook
Either or works
If you’re using React 19 (comes with Next 15) Check the docs for the [useActionState hook](https://react.dev/reference/react/useActionState) (previously named useFormState).
This lets you access to the return value of your action for you to do whatever you want with it.
@Mawhadmd how do i know if the server actions has succeeded?
You can return an object that indicates if the server action succeeded or failed:

return {success : true, data: … }
// or
return {success : false, error: … }


Now in your component get this object back, and if you use useActionState you have a nice loading state too, and you can display your toast or do whatever you need.
adding up to what wolf and luis said, this is how i've done it in the past, if it can help. i used the old useFormState back then, but here i tested it with the new useActionState which has the isPending status which is super nice. i check the result of the action and then show the toast and redirect client side with router.push depending on the result:

"use client";

import { toast } from "sonner";
import { editName } from "../action";
import { useRouter } from "next/navigation";
import { useActionState } from "react";

type State = Awaited<ReturnType<typeof editName>>;

const EditSomeThing = () => {
  const router = useRouter();

  const [state, action, isPending] = useActionState(
    async (_previousState: State, formData: FormData) => {
      const name = formData.get("name");

      if (typeof name !== "string" || !name.trim()) {
        // we can do any client side validation we want here, without contacting the server
        return {
          error: "Name is required",
        };
      }

      // server action is called here
      const result = await editName(name);

      if (result?.error) {
        return result;
      }

      toast("Name edited successfully!");

      router.push("/redirection-testing/users");
    },
    undefined,
  );

  return (
    <form
      action={action}
      className="w-md border-teal-500 bg-teal-50 border-2 p-4 rounded-md fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
    >
      {state?.error && (
        <div className="text-red-500 mb-2">
          <strong>Error:</strong> {state.error}
        </div>
      )}

      <div className="flex gap-2 items-center">
        <label htmlFor="name">Name</label>
        <input
          id="name"
          name="name"
          disabled={isPending}
          className="border-teal-500 border bg-white rounded-md p-1 flex-1"
        />
      </div>

      <button
        type="submit"
        disabled={isPending}
        className="w-full p-2 mt-4 bg-teal-500 text-white font-semibold rounded-md"
      >
        {isPending ? "Loading..." : "Submit"}
      </button>
    </form>
  );
};

export default EditSomeThing;


The action is just:

"use server";

import { setTimeout } from "timers/promises";

export const editName = async (name: string) => {
  await setTimeout(3000);

  // lets randomly fail or success
  if (Math.random() > 0.5) {
    return {
      error: "Failed to edit name",
    };
  }

  console.log("edited", name);
};
@Andres Eloy adding up to what wolf and luis said, this is how i've done it in the past, if it can help. i used the old useFormState back then, but here i tested it with the new useActionState which has the isPending status which is super nice. i check the result of the action and then show the toast and redirect client side with router.push depending on the result: tsx "use client"; import { toast } from "sonner"; import { editName } from "../action"; import { useRouter } from "next/navigation"; import { useActionState } from "react"; type State = Awaited<ReturnType<typeof editName>>; const EditSomeThing = () => { const router = useRouter(); const [state, action, isPending] = useActionState( async (_previousState: State, formData: FormData) => { const name = formData.get("name"); if (typeof name !== "string" || !name.trim()) { // we can do any client side validation we want here, without contacting the server return { error: "Name is required", }; } // server action is called here const result = await editName(name); if (result?.error) { return result; } toast("Name edited successfully!"); router.push("/redirection-testing/users"); }, undefined, ); return ( <form action={action} className="w-md border-teal-500 bg-teal-50 border-2 p-4 rounded-md fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" > {state?.error && ( <div className="text-red-500 mb-2"> <strong>Error:</strong> {state.error} </div> )} <div className="flex gap-2 items-center"> <label htmlFor="name">Name</label> <input id="name" name="name" disabled={isPending} className="border-teal-500 border bg-white rounded-md p-1 flex-1" /> </div> <button type="submit" disabled={isPending} className="w-full p-2 mt-4 bg-teal-500 text-white font-semibold rounded-md" > {isPending ? "Loading..." : "Submit"} </button> </form> ); }; export default EditSomeThing; The action is just: tsx "use server"; import { setTimeout } from "timers/promises"; export const editName = async (name: string) => { await setTimeout(3000); // lets randomly fail or success if (Math.random() > 0.5) { return { error: "Failed to edit name", }; } console.log("edited", name); };
I can't seem to find UseActionState (React 19 beta), Module '"react"' has no exported member 'useActionState'
Thanks for your help btw
version 19 of react should have the useActionState unless you're in an old canary version, if that's your case you can replace it with useFormState importing it from react-dom and it will work similar to the useActionState in the example above (just tested it)

import { useFormState } from "react-dom"; 

in newer versions you will get the deprecation error i got in the picture