This is how you can add subscribe to newsletter feature in your Remix app. You can do it in 2 ways, explained below.
Note: This will not cover the actual subscribe implmenetation, you can use any service you want, like Mailchimp, Sendgrid, etc. The server side code is not integrated so you can easily add your favourite service.
We will use TailwindCSS for styling, but you can use any CSS framework you want.
1. Using Form and Redirect
The first way is to use a form and redirect the user to a thank you page. This is the most common way to do it, but it has a drawback - the user will not be able to go back to the page he was previously on. This is not a big deal if you have a small website, but if you have a lot of pages, it can become a problem.
We will use Remix's Form Component
<Form
method="post"
action="/newsletter/subscribe-with-redirect"
id="subscribe-form-footer"
className="flex justify-center items-center md:w-96 sm:w-72 pt-5 w-full">
<input
type="email"
name="email"
id="email"
placeholder="Your email"
className="w-full h-10 px-3 text-base bg-primary-complement placeholder-white border rounded-lg focus:shadow-outline border-secondary-complement border-r-0 rounded-r-none"
required
/>
<button
type="submit"
value="subscribe"
className="bg-primary-complement rounded-lg border-l-0 rounded-l-none h-10 border-secondary-complement border px-1">
<svg
className="w-6 h-6 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</button>
</Form>
Server handler code:
import { redirect } from '@remix-run/node';
import type { ActionFunction } from '@remix-run/node';
export const action: ActionFunction = async ({ request }) => {
try {
const email = (await request.formData()).get('email');
// throw new Error('test error');
return redirect('/newsletter/thanks', { headers: { 'Set-Cookie': `subscribedEmail=${email}` } });
} catch (error) {
return redirect('/newsletter/error', { headers: { 'Set-Cookie': 'error=true' } });
}
};
From here we need to define 2 new routes:
- thank you page
- error page
Takeaways
Remix's Form component will handle the form submission and the server side will handle the service call, redirection.
This works just like a normal form in a regular HTML page.
This will work even if your JavaScript is disabled.
2. Using useFetcher to show a success message
But maybe you don't want to redirect the user, maybe you want to show a success message on the same page. This is where the useFetcher hook comes in handy.
We will use Remix's useFetcher hook
Most of the code could be also found in Remix Documentation, what this example has extra is that we are showing a success message after the user subscribes.
The success message is display within a modal created with TailwindCSS.
We are also handling the error case and showing a message to the user.
All the code is open source you can find a link to the repo at the end of the article.
import { useEffect, useRef, useState } from 'react';
import { useFetcher } from '@remix-run/react';
import type { FC, ReactElement } from 'react';
import Modal from '~/components/Modal';
const SubscribeToNewsletter: FC = (): ReactElement => {
const newsletter = useFetcher();
const [isModalOpen, setIsModalOpen] = useState(false);
const formRef = useRef<HTMLFormElement>(null);
const toggleModal = (toggler: boolean) => {
setIsModalOpen(toggler);
};
useEffect(() => {
if (newsletter.type === 'done' && newsletter.data.success) {
formRef.current?.reset();
toggleModal(true);
}
}, [newsletter]);
return (
<>
<newsletter.Form
method="post"
ref={formRef}
action="/newsletter/subscribe"
id="subscribe-form-footer"
className="flex justify-center items-center md:w-96 sm:w-72 pt-5 w-full">
<input
type="email"
name="email"
id="email"
placeholder="Your email"
className="w-full h-10 px-3 text-base bg-primary-complement placeholder-white border rounded-lg focus:shadow-outline border-secondary-complement border-r-0 rounded-r-none"
required
/>
<button
type="submit"
value="subscribe"
disabled={newsletter.state === 'submitting'}
className={`bg-primary-complement rounded-lg border-l-0 rounded-l-none h-10 border-secondary-complement border px-1 ${
newsletter.state === 'submitting' ? 'opacity-70' : 'opacity-100'
}`}>
<svg
className="w-6 h-6 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
</button>
{newsletter.type === 'done' && newsletter.data.error && (
<span className="absolute text-red-700 mt-20">Something went wrong ...</span>
)}
</newsletter.Form>
<Modal
isOpen={isModalOpen}
onClose={() => {
toggleModal(false);
}}
title={'Subscribed!'}
subtitle={`Thank you ${
newsletter.type === 'done' && newsletter.data.subscribedEmail
}. You are now subscribed to our newsletter.`}
/>
</>
);
};
export default SubscribeToNewsletter;
Server handler code:
import { json } from '@remix-run/node';
import type { ActionFunction } from '@remix-run/node';
export const action: ActionFunction = async ({ request }) => {
try {
const email = (await request.formData()).get('email');
// throw new Error('test error');
return {
success: true,
subscribedEmail: email,
};
} catch (error) {
return json({ error: true });
}
};
Note how here we are using the json function from Remix to return a JSON response. This is because we are not redirecting the user, we are just returning a JSON response. This comes handy when we want to return different data from a server side action.
The 2nd case won't work if you have JavaScript disabled, but the first one will.
See it in action
I created a playground (https://playground.robipop.dev) where you can see the examples in action.
The playground is open source you can see it here:
https://github.com/robipop22/playground
Conclusion
Remix is a great tool for building modern web applications. It's easy to use and it's very flexible. A benefit is that all of these are built on top of Web Standards.
I hope you enjoyed this article and learned something new. If you have any questions or suggestions please reach out to me via twitter/email.
Keep on coding! 🚀