Server actions in React can be tricky to manage, especially when it comes to providing users with feedback on the status of their action. In this article, we’ll explore a clean and user-friendly approach to handling React server actions using react-hot-toast.
Table of contents
Why Use react-hot-toast?
I was using traditional server actions with <form action="">
and struggling to provide good user feedback. Then I discovered this cool method, and I hope you’ll find it helpful too!
react-hot-toast
is a lightweight, customizable, and beautiful toast notification library for React. It’s perfect for showing real-time feedback to users during async actions. Using its toast.promise()
method, you can manage loading, success, and error states in one neat package.
Example Use Case
Let’s create a simple newsletter subscription form. Users will enter their email and click the Subscribe button. Here’s how we’ll handle it:
- Show a loading state when the form is submitted.
- Display a success message if the subscription is successful.
- Handle errors gracefully with a descriptive error message.
Here’s the complete code implementation.
Step 1: Define a Response Interface
- Before diving into the implementation, let’s define the structure of our server’s response:
interface ActionResponse {
status: 'success' | 'error';
message: string;
data?: any;
}
This ensures consistency and clarity when working with server responses.
Step 2: The Frontend - Subscribe Component
- In the
Subscribe
component, we’ll usereact-hot-toast
to handle feedback during the subscription process:
"use client"
const handleSubscribe = () => {
const result = subscribeToNewsletterAction(values);
toast.promise(result, {
loading: 'Subscribing...',
success: (data: ActionResponse) => {
setLoading(false);
if (data.status === 'success') {
form.reset();
return data.message;
}
throw new Error(data.message);
},
error: (err: any) => err.toString(),
});
}
Here’s what’s happening:
- Loading: As soon as the user submits the form, they see a “Subscribing…” message.
- Success: If the server responds with a success status, the form is reset, and a confirmation message is displayed.
- Error: If there’s an issue, the error message from the server is shown instead.
Step 3: The Backend - Action Handler
- Now, let’s create the server-side function that processes the subscription:
export const subscribeToNewsletterAction = async (payload: z.infer<typeof newsletterSchema>): Promise<ActionResponse> => {
const input = newsletterSchema.parse(payload);
try {
// subscribe
return {
message: 'Thank you for subscribing! You’ll now receive the latest updates straight to your inbox.',
status: 'success'
}
} catch (error) {
return {
message: 'Oops! Something went wrong while processing your request. Please try again later.',
status: 'error'
}
}
}
Here:
- We validate the user’s input using Zod.
- We use
ActionResponse
return type for typesafe - If the subscription is successful, a success message is returned.
- If there’s an error, an appropriate error message is sent back.
Security Considerations
Server actions are essentially public-facing endpoints. Avoid returning any sensitive data in the response. If necessary, implement proper authentication and authorization checks before executing the code to ensure secure access.
Step 4: Custom Toast Styling
- You can customize every aspect of your toasts:
toast.promise(result, {
loading: {
message: 'Working on it...',
icon: '⚡️',
style: {
background: '#2196F3',
color: 'white',
},
},
success: {
message: data => `${data.message} 🎉`,
duration: 4000,
icon: '🌟',
},
error: {
message: err => getErrorMessage(err),
duration: 5000,
icon: '💫',
},
});
For further options and details, check out https://react-hot-toast.com/
Conclusion
By combining React Server Actions with react-hot-toast
, we've created a robust, user-friendly system for handling asynchronous operations. This approach not only improves the user experience but also makes our code more maintainable and type-safe
.