Skip to content

Micro Frontends: Shared State in Micro Frontends

Sharing state across Micro Frontends can be achieved using libraries like Redux or custom state management solutions.

Managing shared state across micro frontends is crucial for ensuring consistent user experience and data flow. Sharing state can be tricky because each micro frontend is typically built as an isolated, independent module. Below are strategies and tools to help achieve shared state management in a micro frontend architecture.


⚙️ Strategies for Sharing State

1. Global Event Bus

A global event bus allows micro frontends to communicate through events. This is a decoupled approach where state is not directly shared, but events trigger actions in different micro frontends.

Example
// Event Bus Implementation
const eventBus = {
  events: {},
  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  },
  publish(event, data) {
    if (this.events[event]) {
      this.events[event].forEach((callback) => callback(data));
    }
  },
};
Usage
// Micro Frontend 1 (Publisher)
eventBus.publish("USER_LOGIN", { userId: 1 });

// Micro Frontend 2 (Subscriber)
eventBus.subscribe("USER_LOGIN", (data) => {
  console.log("User logged in:", data.userId);
});

2. Shared State Libraries (Redux, Zustand)

A more robust solution is to use a shared state management library, such as Redux or Zustand, across your micro frontends. This can be done by creating a shared package that all micro frontends can import.

🔧 Example with Redux
  1. Create a shared package:

    pnpm add @reduxjs/toolkit react-redux
    
  2. Define a shared store:

    // shared/store.js
    import { configureStore, createSlice } from '@reduxjs/toolkit';
    
    const userSlice = createSlice({
        name: 'user',
        initialState: { isLoggedIn: false, user: null },
        reducers: {
            login: (state, action) => {
                state.isLoggedIn = true;
                state.user = action.payload;
            },
            logout: state => {
                state.isLoggedIn = false;
                state.user = null;
            },
        },
    });
    
    export const { login, logout } = userSlice.actions;
    
    export const store = configureStore({
        reducer: {
            user: userSlice.reducer,
        },
    });
    
  3. Use the shared store in micro frontends:

    // Micro Frontend 1
    import { store, login } from 'shared/store';
    store.dispatch(login({ name: 'John Doe' }));
    
    // Micro Frontend 2
    import { useSelector } from 'react-redux';
    const user = useSelector(state => state.user);
    console.log(user);
    

3. Module Federation with Shared State

When using Module Federation, you can share state by exposing a central state management module that other micro frontends can consume.

🔧 Example
// webpack.config.js (Host App)
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "host",
      remotes: {
        mfe1: "mfe1@http://localhost:3001/remoteEntry.js",
      },
      shared: {
        react: { singleton: true },
        "react-redux": { singleton: true },
      },
    }),
  ],
};

🧰 Tools for Shared State

  • Redux Toolkit: Simplifies shared state management across micro frontends.
  • Zustand: A lightweight alternative to Redux for managing state.
  • Recoil: State management library by Facebook, ideal for micro frontends.
  • RxJS: Enables reactive state sharing across micro frontends through observables.

🛠 Best Practices

  1. Avoid tight coupling: Ensure micro frontends remain independent and only rely on shared state when necessary.
  2. Use lazy loading: Load shared state only when required to optimize performance.
  3. Handle versioning carefully: Ensure all micro frontends use compatible versions of shared libraries.

By implementing one of these strategies, you can efficiently share state across your micro frontends, improving data consistency and the overall user experience.