We all know of the success and growth of morning newsletter darling Morning Brew. Started by Alex Leiberman, Morning Brew now garners over 25 million subscribers and recently sold a majority piece of their business for $70M to Business Insider. The entire business is a curated email marketing subscription delivering a tailored “bring-you-up-to-speed” email every morning. As executives ourselves, we can say it’s great.
Inspired by the simplicity of this business model, we decided to use a Morning Brew-like email subscription service as the foundation of our tutorial, which showcases the power, flexibility and scalability of Zustand, a mean and lean state management technology that should be a new addition to every frontend developer's toolkit.
This tutorial is a 3-part series and will give a detailed walk through of how to do state management on the component level with Zustand. We'll show how to use Zustand in a tactically relevant way, while creating a fully integrated React component.
Here's a breakdown of what we'll be covering throughout the series:
As a caveat, each part above is linked to a Code Sandbox, complete with the section's fully completed code, for convenience. To make the most use of your time while following this tutorial, we recommend opening and forking the part's sandbox at the beginning of the section in a separate tab. Our Code Sandbox example can be your 'target repo'. While you're completing each part of the tutorial, your goal should be to write code that eventually resembles the target.
Basic knowledge of React and Node.js is required to follow this tutorial.
In Part 3 of the tutorial, you'll also need a Buildable Developer account and SendGrid account (or another email API of choice).
To demonstrate Zustand, we'll be building a Morning Brew replica web app. The entire build can be completed in 20 minutes or lessand, more importantly, will be fully connected. This means our app's React component will have seamless state transitions, be connected to a microservice mesh (that you'll spin up) to store collected leads (i.e., emails) and have a fully deployed backend for the email triggers. We'll even add a skeleton loader to the React component to pretty it up when data is loading!
When we get to connecting dynamic data to the component (Part 3 of the tutorial), you'll find we use a Buildable Recipe for the connection. This is important because we want to make sure our developers avoid as many deployments as possible when we push this component to production. In production, DevOps is always a breaking point for most engineering teams, so we're going to work in tips and tricks using these tools that showcase exactly how to smooth out that process and keep our deployment processes simple.
Here's Morning Brew's landing page that collects emails and sends a confirmation email to the subscriber:
This is how our Morning Brew replica will look:
In the image above, you can see the user submitting their email to subscribe to the newsletter. On user submission, we'll be storing this user's email and triggering the delivery of a welcome email to the same user. Let's get started.
We're going to assume you're starting from an empty React app (created via create-react-app), so let's begin there.
The first thing we need to do is set up our project. We're first going to install the following packages:
We're going to set ourselves up for success with a clean and organized folder structure, which is as follows:
For consistency's sake, we're going to inject an app-wide theme into the theme.js file.
Now that we're all set up, we're going to start laying the foundation of our component. Let's start by heading to the components/email-block folder and creating the following files: Button.js, EmailBlock.js and index.js
We're going to quickly style the material-ui button for sake of simplicity, while also matching the higher standard of aesthetic that we're looking for. In Button.js, add the following code snippet:
Now, we're going to create and export a functional React card component that will eventually connect to Zustand to manage the component's state. This component will be the centre of our attention as we move into the next phase. In EmailBlock.js, add the following code snippet:
Now, let's now head to the src/App.js file. In this file, we're simply going to import the email-block and add a background image. We're also going to add our app's logo in src/assets/images. Here's the code snippet:
We're now going to head back to the EmailBlock.js to start assembling the structure of the React component. The component will include a title, subtitle, input field and a button. We'll be using Grommet's Heading, Grommet's Text, Material-UI TextField and the styled button we created earlier. In the EmailBlock.js file, add the following code snippet:
At this stage, we have the fully assembled base structure of our React component 👏 and our app now looks like this 👇
We plan on using Zustand and a Buildable Recipe to load the content of this component dynamically, while layering in the backend logic, so it'll be very helpful to add a loading state to the component. Skeleton loaders are often one of the most subtle additions to any UI. Yet, they are responsible for giving the user a pleasant experience whenever we have to wait for data to load. We like pleasantries, so let's add one.
Here's how the component will look when loading data using the skeleton loader:
To add our skeleton loader, let's head to src/components/email-block folder and add a LoadingBlock.js file. In the file, add the following code snippet:
Now, let's head back to the EmailBlock.js file to add the loading block component and to create a loading simulation.
In the above code, we created a wrapper box to give us the same exact look and feel regardless of whether we're showing data or in a load state. We're also using React useState isLoading to simulate a loading state. The default is set to true. We're also adding a useEffect to help us remove the loading state after two seconds.
Once this is complete, your component should start in a loading state and should clear after two seconds.
Head back to the LoadingBlock.js file and add the following:
That’s it! In this part of the series, you set up a clean and organized React component and layered in a beautiful skeleton loader.
Head to Part 2 of this tutorial which is where we will implement Zustand!
You can find the app’s finished up until this point on this Code Sandbox.