Grow your mailing list: Mailchimp x React x Firebase
Tags: webdev, react, firebase, tutorial
Published: 2023-10-13 02:06:00

For this tutorial, we're gonna make a React Subscriber form, which adds someone to an audience list in Mailchimp. We'll break it down to these steps:

1. Create React form
2. Create Mailchimp account
3. Create serverless Mailchimp API function
4. Combine the two

As always, I like to add a bit of context to the tutorial before starting, but if you want to skip ahead, [feel free](#tutorial). 

A popular addition that I normally add to websites I create are Subscriber forms, and typically I use Mailchimp, since it's easy to work with and useful overall. Since it's a great tool to utilize, I felt like making a quick tutorial on how to set it up. I also wanted to make sure that you could either come away with:
1. a reusable form that you can add to other projects
2. a serverless function that you can call for other use cases
3. why not both?

![Cheering gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iimv0ljcn4qea64l313p.gif)

For the frontend, we will utilize Material UI to create the form and Firebase Functions to create the serverless function. [Material UI](https://mui.com/material-ui/) is a component library that makes it much easier to create a form that you can continue to build upon afterwards. And [Firebase Functions](https://firebase.google.com/docs/functions) is a serverless framework where we can run store our backend code and trigger our functions upon HTTPS requests. To answer any potential questions.
1. Why Firebase? Because I'm familiar with it and the project I was working on already had a Firebase project set up. You can use another Serverless architecture, or even run it locally with an Express server. But this tutorial will use Firebase.
2. Why even make a serverless function? Since Firebase will be using an API Key, keeping it stored in a serverless architecture can be much safer than having it in the client, especially if there's plans for this function in the future. So we're going with best practices.
Any other questions, please ask in the comments and I'll answer! But anyways, let's jump into the tutorial.


## Create Simple Input Form <a name="tutorial"></a>

Firstly, let's create our standard React project by running
`npx create-react-app mailchimp-form`. Once it finishes, run `npm install @mui/material @emotion/react @emotion/styled` to install the dependencies for what we need for the frontend.

Once it's created, let's remove the code inside the App.jsx file so that it's pretty barebones. It should look like:

```
import React from 'react';

function App() {

return (
    <div>Hello!</>
)

export default App;
```

So now let's create the form to get all the user info we want. Let's add the following to the App.jsx file:

```
import React, { useState } from 'react';
import { TextField, Button, Container, Stack } from '@mui/material';

function App() {
  return (
    <Container>
      <h2>Mailchimp Form</h2>
      <form onSubmit={handleSubmit} >
        <Stack spacing={{ xs: 3, sm: 2 }} direction={{ xs: 'column', sm: 'row' }} sx={{mb: 3}}>
          <TextField
            name='firstName'
            type="text"
            variant='outlined'
            label="First Name"
            onChange={handleUserChange}
            value={firstName}
            fullWidth
            required
          />
          <TextField
            name='lastName'
            type="text"
            variant='outlined'
            label="Last Name"
            onChange={handleUserChange}
            value={lastName}
            fullWidth
            required
          />
        </Stack>
        <TextField
          name='email'
          type="email"
          variant='outlined'
          label="Email"
          onChange={handleUserChange}
          value={email}
          fullWidth
          required
          sx={{mb: 3}}
        />
        <Button variant="outlined" type="submit">Subscribe</Button>
      </form>
    </Container>
  )
}

export default App;

```

And if we see the app, it should look like this:

![Mailchimp Form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jhfpjhy93sc4o0osknrb.png)

Great, so now we have our form showing up, but at the moment, it doesn't keep track of the user input. So let's add that piece, let's add `useState` to fully keep track of what a user inputs.
```
const [user, setUser] = useState({
    firstName: '',
    lastName: '',
    email: '',
});

const { firstName, lastName, email } = user;
```

Now for the form to update the state of our userInfo, we'll add this function.
```
  const handleUserChange = (e) => {
    setUser({
      ...user,
      [e.target.name]: e.target.value
    })
  };
```

Lastly, let's add a function to handle the submission when the user clicks the subscribe button:
```
const handleSubmit = async (e) => {
    e.preventDefault();
    console.log(user);
};
```

Now, our code should look like:
```
import React, { useState } from 'react';
import { TextField, Button, Container, Stack, Snackbar, Alert, Skeleton } from '@mui/material';

function App() {

  const [user, setUser] = useState({
    firstName: '',
    lastName: '',
    email: '',
  });

  const { firstName, lastName, email } = user;

  const handleUserChange = (e) => {
    setUser({
      ...user,
      [e.target.name]: e.target.value
    })
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    console.log(user);
  };
 
  return (
    <Container>
      <h2>Mailchimp Form</h2>
      <form onSubmit={handleSubmit} >
        <Stack spacing={{ xs: 3, sm: 2 }} direction={{ xs: 'column', sm: 'row' }} sx={{mb: 3}}>
          <TextField
            name='firstName'
            type="text"
            variant='outlined'
            label="First Name"
            onChange={handleUserChange}
            value={firstName}
            fullWidth
            required
          />
          <TextField
            name='lastName'
            type="text"
            variant='outlined'
            label="Last Name"
            onChange={handleUserChange}
            value={lastName}
            fullWidth
            required
          />
        </Stack>
        <TextField
          name='email'
          type="email"
          variant='outlined'
          label="Email"
          onChange={handleUserChange}
          value={email}
          fullWidth
          required
          sx={{mb: 3}}
        />
        <Button variant="outlined" type="submit">Subscribe</Button>
      </form>
    </Container>
  )
}

export default App;

```

With this, our form takes, stores, and submits the user input! But submits to what? Currently, nothing. But then enters Mailchimp.

## Create Mailchimp Account

Let's create a Mailchimp audience form. Now first we need to go to Mailchimp.com and sign up for an account. If you already have an account, awesome! If not, no worries. When you sign up, it does ask for a your business email and business address. It does need this info for anti-spam laws. If you don't have a business location, you can use one of the [alternate address suggestions](https://mailchimp.com/help/alternative-physical-address-ideas/) that it recommends. Or you can just skip this part entirely, bookmark it, and use it in the future when you create one for yourself or a future client.
Once your account is setup, go to the Audience Tab -> Signup Forms. On this page, go to Settings -> Audience Name and Defaults. On this page, you should see an Audience ID, which is what we'll need in the next step, so take note of it.
Now one more thing we'll want to check out is in Settings -> Audience fields and *MERGE* tags. 
![Merge tags screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6nkmfeybygrm2mpalpzi.png)

Here we can see the fields that we can collect from our subscribers, and add any additional ones. We'll stick with the ones we have here, which is First Name, Last Name, and Email Address.

## Create Serverless function

Now that we have our form and Mailchimp account set up, let's connect to it with a serverless function using Firebase Functions. 

Now we'll initialize a firebase project. I'll write the quick steps here but for a description of the steps and more about firebase I'll point you to [the documentation](https://firebase.google.com/docs/functions/get-started?gen=2nd)
1. Create Project in the [Firebase Console](https://console.firebase.google.com/)
2. Install the Firebase CLI with `npm install -g firebase-tools`
3. Log into Firebase from your terminal with `firebase login`
4. At the root of our project, run `firebase init functions`, select Javascript for our language, and install the dependencies

Now that should set us up. Now we'll add our function into the index.js file within the functions directory:

```

exports.addSubscriber = functions
.runWith({ secrets: ["MAILCHIMP_SECRET_KEY"] })
.https.onCall(async (data, context) => {
  const audienceId = ""; // add your audienceId here

  const {firstName, lastName, email} = data;
  const mailchimp = new Mailchimp(process.env.MAILCHIMP_SECRET_KEY);
  try {
    const resp = await mailchimp.post(`/lists/${audienceId}/members`, {
      merge_fields: {
        "FNAME": firstName,
        "LNAME": lastName
      },
      email_address: email,
      status: "subscribed"
    });
    return resp;
  } catch (error) {
    throw new functions.https.HttpsError("unknown", error);
  }
});
```
So all that our function is doing is destructing our request body for the parameters that we'll use for the Mailchimp call, and then returning a response indicating if it's successful.
Now one thing to note, we'll have to add that MAILCHIMP_SECRET_KEY to your secrets in Firebase so before you deploy, you'll just add the secret with `firebase functions:secrets:set MAILCHIMP_SECRET_KEY` and then paste the key. Afterwards, you can deploy with `npm run deploy` and once it succeeds, you should see the URL of the function that we'll use with our form. 
If you want to just run it locally, first you can remove the `.runWith({ secrets: ["MAILCHIMP_SECRET_KEY"] })` line and paste your key in place of **process.env.MAILCHIMP**. And then you can run `npm run serve` to start the function emulator, which will give you a URL for the function that you can use locally.
To test this, we can send a request in Postman with a request body of:
```
{
    "data": {
        "firstName": "Mark",
        "lastName": "Woodson",
        "email": "mark@markwoodson.me"
    }
}
```
You can verify the success of the request through the response or checking your Mailchimp Audience list to see if it grew by one!

_Side note: The reason the request is in a field called 'data' is because the way we created the Firebase function. By using the "onCall" method, the request needs to be sent in that format. Using "onRequest", you can make the request without that field at the top level._

Alright, now we got two pretty useful, but separate, components. But how about we combine them into one awesome piece. 

## Bring It All Together

Let's make the call to our Firebase function. All we need to do is make a change to our submit function. Let's change the submit function to:
```
const handleSubmit = async (e) => {
    e.preventDefault();
    fetch('http://127.0.0.1:5001/mailchimp-form/us-central1/addSubscriber', {
      method: 'POST',
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ firstName, lastName, email }),
    })
    .then((response) => response.json())
    .then((data) => {
      if (data.status === 'subscribed') {
        setUser({
          firstName: '',
          lastName: '',
          email: '',
        });
      } else {
        console.error('Error:', data);
      }
    })
    .catch((error) => {
      console.error('Error:', error);
    });
  };
```

After running the app, you should be good to go!

## Next Steps
Awesome work on this. I hope it worked smoothly and will serve you well in the future! Now this definitely can be improved with a couple of things, like error handling, styling, additional form inputs, etc. But if you had any comments, questions, or concerns, please let me know in the comments and check out my other work at [markwoodson.me](https://markwoodson.me/)