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:
- Module Federation is meant to share any JS/TS module not just UI component.
- 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
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 exposedhost
: it will consume the store fromapp1
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;
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>();
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"],
},
},
};
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;
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>
);
});
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.
Top comments (0)