DEV Community

Ibrahim Shamma
Ibrahim Shamma

Posted on • Edited on

A simplified prospective in sharing Redux Store between Federated React Apps

You have already created multiple federated react apps, and you still wondering, how can I share not just components in the runtime, but the entire store between the apps.

There are two notes here to consider:

  1. Module Federation is meant to share any JS/TS module not just UI component.
  2. The key into sharing the store is by exposing the reducers from our app into the host.

Work in action!!

Firstly clone the repo, or click here to navigate through the code without installing

git clone https://github.com/IbrahimShamma99/gentle-intro-module-federation-react 
cd ./gentle-intro-module-federation-react
Enter fullscreen mode Exit fullscreen mode

Now let's examine the Repo, we have two react apps built with webpack, app1 and host

  • app1: will contain the store that will be exposed

  • host: it will consume the store from app1

So now how can we share the store?

Remote set up

While we are preparing the store in order to be consumed, we are here preparing a normal store that will consumed normally inside the remote (remote and app1 are the same I will use both interchangeably), and we will expose the reduces and that's it!

Exposing the Store in action!

first let's set up a store and reducer inside the remote
Firstly we are setting up a layout slice that will handle storing and toggling theme.

// apps/app1/src/reducer.ts

import { createSlice } from "@reduxjs/toolkit";

type Theme = "light" | "dark";

export interface LayoutState {
  theme: Theme;
}

const initialState: LayoutState = {
  theme: "light",
};

const layoutSlice = createSlice({
  name: "layout",
  initialState,
  reducers: {
    toggleTheme: (state, action) => {
      state.theme = action.payload as Theme;
    },
  },
});

export const { toggleTheme } = layoutSlice.actions;

export { layoutSlice };

export default layoutSlice;
Enter fullscreen mode Exit fullscreen mode

And Now besides the reducer, we will create the store, although we will not share the store between apps, since redux only combines reducers not stores, but the store is crucial for the mutating the reducer inside the remote

NOTE: You can expose the reducers and be able to access the store, but preferably don't use actions outside of the remote

To create the remote store by simply:

import { configureStore } from "@reduxjs/toolkit";
import { useDispatch } from "react-redux";
import { combineReducers } from "redux";
import { layoutSlice } from "./reducer";

const Store = configureStore({
  reducer: combineReducers({
    layout: layoutSlice.reducer,
  }),
});

export { Store };
export type RootState = ReturnType<typeof Store.getState>;
export type AppDispatch = typeof Store.dispatch;
export const useAppDispatch = () => useDispatch<AppDispatch>();
Enter fullscreen mode Exit fullscreen mode

now in module federation remote app configuration we need to do the following

// apps/app1/configs/federationConfig.js
const dependencies = require("../package.json").dependencies;

module.exports = {
  name: "app1",
  filename: "remoteEntry.js",
//This is what is important, exposing our reducer
  exposes: {
    "./layout-slice": "./src/reducer",
  },
  shared: {
    ...dependencies,
    react: {
      singleton: true,
      requiredVersion: dependencies["react"],
    },
    "react-dom": {
      singleton: true,
      requiredVersion: dependencies["react-dom"],
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Now we have finished setting up and exposing the remote store, now we go into the host part

Host set up

Now we need to do one unconventional thing in the redux world, instead of importing all of our reducers like what we do normally synchronously, we will dynamically build the store, and we will have two approaches

  • When loading the page wait until the Store is imported then continue upon rendering.
  • Load Redux microfrontend framework Click Here

we will use the first approach since the latter needs a blog by its own.

we need to set up the host store now:

//apps/host/src/store.tsx

import { configureStore } from "@reduxjs/toolkit";
import { combineReducers } from "redux";

/*
NOTE: 
Here you take the interface of each slice and pass them into the `useStoreSelector` Hook in this way your `intellisense` will be aware of the federated types.
*/
const federatedSlices = {
  layout: await import("app1/layout-slice").then(
    (module) => module.default.reducer
  ),
};

const initStore = async () => {
  const Store = configureStore({
    reducer: combineReducers({
      ...federatedSlices,
    }),
  });
  return Store;
};

export default initStore;
Enter fullscreen mode Exit fullscreen mode

As you can see in the code the store is initialized asynchronously, so the dom will be waiting our initStore promise to be fulfilled:

//apps/host/src/bootstrap.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { Provider } from "react-redux";
import initStore from "./store";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);

initStore().then((Store) => {
  root.render(
    <Provider store={Store}>
      <App />
    </Provider>
  );
});
Enter fullscreen mode Exit fullscreen mode

NOTE make sure that the host already connected into the remote via config, if you could not make a lot of sense here, better to look up the following article.

Cheers!

Top comments (0)