Best practice for showing a toast after revalidating a path
Answered
Palm Warbler posted this in #help-forum
Palm WarblerOP
What's the best pattern to show client component elements like toasts on a page that displays both data (server component) and a form to save data (client component).
I want to show a success toast after performing a form action. I use
How to get around this? I noticed that often only the first form submit reloads the page and next items actually show toasts. I can't get to the bottom of this. Below is the code for the page, components and the action.
Code in a following comment due to character limit.
I want to show a success toast after performing a form action. I use
useFormState in a useEffect hook to show a toast but I also need to revalidatePath in the form action to show new data on a page. Revalidation reloads the page causing the toasts to never show up.How to get around this? I noticed that often only the first form submit reloads the page and next items actually show toasts. I can't get to the bottom of this. Below is the code for the page, components and the action.
Code in a following comment due to character limit.
Answered by B33fb0n3
yes it is. The toast will be showed even when the page is revalidating. Because, revalidating does not reload the page. It reads and fetch all the code again, but only refresh the specific parts, that were changed on your client page. So the toast stays there even if we revalidate
13 Replies
Palm WarblerOP
page:
action:
import createServerClient from '@/utils/server';
import TestForm from './formTest';
export default async function Test() {
const supabase = await createServerClient();
const { data: items } = await supabase.from('items').select('*');
return (
<section className="mb-32">
<TestForm />
<ul>
{
items?.map((item) => (
<li key={item.id}>
<span>{item.name}</span>
</li>
))
}
</ul>
</section>
);
}action:
'use server';
const createTest = async (prevState: any, formData: FormData) => {
// save in the DB etc...
console.log('createTest', formData);
revalidatePath('/test');
return {
status: 200,
message: 'Item created',
};
};components:
'use client';
import { useState, useEffect } from 'react';
import { useFormState } from 'react-dom';
import { notifications } from '@mantine/notifications';
import { TextInput } from '@mantine/core';
import { createTest } from '@/actions/actions';
import FormControls from './formControls';
const initialFormMessageState = {
status: 200,
message: '',
};
const FormTest: React.FC = () => {
const [name, setName] = useState('');
const [createFormState, createFormAction] = useFormState(
createTest,
initialFormMessageState
);
// Manage notification toasts
useEffect(() => {
console.log(createFormState);
if (
createFormState?.status === 200 &&
createFormState?.message
) {
notifications.show({
title: createFormState?.message ?? 'Item created',
message: 'New entry created successfully',
color: 'green'
});
}
if (
createFormState?.status === 400 &&
createFormState?.message
) {
notifications.show({
title: createFormState?.message ?? 'Error creating an item',
message: 'Error!',
color: 'red',
autoClose: false
});
}
}, [createFormState]);
return (
<div className="flex mt-8 justify-center items-center flex-col">
<form
action={createFormAction}
className={`flex gap-2 items-center flex-wrap`}
>
<TextInput
name="itemName"
placeholder="Name"
label="Name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
<FormControls />
</form>
</div>
);
};
export default FormTest;import React from 'react';
import { useFormStatus } from 'react-dom';
import { Button } from '@mantine/core';
const FormControls: React.FC = () => {
const { pending } = useFormStatus();
return (
<Button
className="flex-shrink-0"
type="submit"
loading={pending}
>
Add new
</Button>
);
};
export default FormControls;@Palm Warbler components:
js
'use client';
import { useState, useEffect } from 'react';
import { useFormState } from 'react-dom';
import { notifications } from '@mantine/notifications';
import { TextInput } from '@mantine/core';
import { createTest } from '@/actions/actions';
import FormControls from './formControls';
const initialFormMessageState = {
status: 200,
message: '',
};
const FormTest: React.FC = () => {
const [name, setName] = useState('');
const [createFormState, createFormAction] = useFormState(
createTest,
initialFormMessageState
);
// Manage notification toasts
useEffect(() => {
console.log(createFormState);
if (
createFormState?.status === 200 &&
createFormState?.message
) {
notifications.show({
title: createFormState?.message ?? 'Item created',
message: 'New entry created successfully',
color: 'green'
});
}
if (
createFormState?.status === 400 &&
createFormState?.message
) {
notifications.show({
title: createFormState?.message ?? 'Error creating an item',
message: 'Error!',
color: 'red',
autoClose: false
});
}
}, [createFormState]);
return (
<div className="flex mt-8 justify-center items-center flex-col">
<form
action={createFormAction}
className={`flex gap-2 items-center flex-wrap`}
>
<TextInput
name="itemName"
placeholder="Name"
label="Name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
<FormControls />
</form>
</div>
);
};
export default FormTest;
this is how I like to do it:
The revalidate is inside my server action. Pretty easy if you ask me 👍
const {execute, status} = useAction(someServerAction, {
onSuccess: async (data, input, reset) => {
toast.success("Saved.")
},
onError: handleActionError,
});The revalidate is inside my server action. Pretty easy if you ask me 👍
@Palm Warbler solved?
Palm WarblerOP
where is this hook coming from? Is it you custom hook?
that's next-safe-action isn't it?
Palm WarblerOP
do you revalidate the page in that server action? Or do you add the data manually to the page that the user is on?
I think that while this is very convenient to use, it doesn't solve my problem that 1/ revalidating a page reloads it and prevents toasts to show up or 2/ having a stale page if I'm not revalidating
@Palm Warbler that's next-safe-action isn't it?
yes it is. The toast will be showed even when the page is revalidating. Because, revalidating does not reload the page. It reads and fetch all the code again, but only refresh the specific parts, that were changed on your client page. So the toast stays there even if we revalidate
Answer
Palm WarblerOP
thanks, I will give it a try today again
Palm WarblerOP
Looks like this fixed my problem, thanks for help @B33fb0n3 👍