← Back To Blog

Using Zustand in a Micro Frontend

Jun 2, 20223 min read

As you develop a large-scale Micro Frontend application built using Webpack’s Module Federation, you will eventually need to share data and communicate between your micro-apps. I will cover the many options available in later articles but first I want to share with you an example of using a state management solution.

Note: Sharing global state in a Micro Frontend architecture should be a last resort and carefully designed as it introduces unnecessary coupling between your teams.

Zustand is a relatively new state management tool still early in its lifecycle with only ~470k weekly downloads, but it has been picking up steam rapidly. It is an elegant and fun-to-use solution I urge you to try.

How could we build a Zustand store that can be used across our micro apps?

Creating a shared Zustand store

First, we need to instantiate a Zustand store in one of our micro-apps. It can be any that exposes remote modules with Module Federation.

// src/store.js
import create from "zustand";
export const useStore = create((set) => ({
cart: [],
addItem: (newItem) => {
set((state) => {
const cart = [...state.cart];
cart.push(newItem);
return { cart };
});
},
removeItem: (itemName) => {
set((state) => {
const cart = [...state.cart];
const existingIndex = cart.findIndex((item) => item.name === itemName);
if (existingIndex > -1) {
cart.splice(existingIndex, 1);
}
return { cart };
});
},
}));
export default useStore;

In this example, we have a store tracking some form of cart with methods to add or remove items from the cart.

Notice we instantiated the store by creating a useStore hook. This is what we will expose to the other apps.

Next, we update our webpack.config.js:

plugins: [
new ModuleFederationPolugin({
name: "Cart",
filename: "remoteEntry.js",
exposes: {
"./cartStore": "./src/store",
},
}),
],

Now we're ready to use the store in our other micro apps:

import { useStore } from "Cart/cartStore";
export const Product = ({ name }) => {
const cart = useStore((state) => state.cart);
const addItem = useStore((state) => state.addItem);
const removeItem = useStore((state) => state.removeItem);
const itemInCart = cart.findIndex((item) => item.name === name);
if (itemInCart) {
return (
<button onClick={() => removeItem({ name })}>Remove from cart</button>
);
}
return <button onClick={() => addItem(item)}>Add to Cart</button>;
};

And voila! You have created a global state in a micro frontend architecture. Congratulations!

Live Example

Check out my Micro Frontend Demo app:https://micro-frontend-demo-main.vercel.app/

Store

It’s a dummy e-commerce site for buying different types of fruit. It consists of a host app and two remotes: cart and products. The cart remote exposes a Zustand store for managing items in your cart and adding and removing them. The product remote consumes it, so it knows which items have been added to the cart and how many, allowing you to change the quantity or remove/add items. As a bonus, the Zustand store in this case stores the cart state in local storage and hydrates on page load so we can persist your cart across sessions.

You can check out the source code here: link

Whats next?

Global state management isn't the only type of state we need in a Micro Frontend. In fact it often should be your last resort because of the added complexity. Stay tuned for a follow up post about more generally how best to manage different types of state in a micro frontend architecture.