Reputation: 1711
How to properly setup ModuleFederation and react-router-dom
so that I can have
Router
and routes defined in Host
appHeader
app has <Link>
components pointing to the routes defined in Host?However, the setup below fails to the following error:
index.js:15 Uncaught Error: useHref() may be used only in the context of a <Router> component.
The setup:
Host mfe app, localhost:3001
...
import { BrowserRouter } from 'react-router-dom'
const Header = lazy(() => import("header/Header"))
const Host = () => {
return (
<BrowserRouter>
<React.Suspense fallback="Loading Header...">
<Header />
</React.Suspense>
<Switch>
<Route path="/input">
<InputFormView />
</Route>
<Route path="/">
<ListView />
</Route>
</Switch>
</BrowserRouter>)
}
...
Host's webpack.config.js
...
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
header: 'header@http://localhost:3002/remoteEntry.js'
},
exposes: {
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
"react-router-dom": {
singleton: true,
requiredVersion: deps["react-router-dom"],
}
},
}),
...
Header mfe app, localhost:3002
...
import { Link } from 'react-router-dom'
const Header = () => {
return (
<div id="header">
<h1> Header </h1>
<Link to="/input">
<button type="button"> Input form </button>
</Link>
</div>
)
...
Header's webpack.config.js
...
new ModuleFederationPlugin({
name: 'header',
filename: 'remoteEntry.js',
exposes: {
'./Header': './src/Components/Header'
},
remotes: {},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
"react-router-dom": {
singleton: true,
requiredVersion: deps["react-router-dom"],
}
},
}),
...
But, if I wrap also Header
in BrowserRouter
I encounter the following error instead:
index.js:15 Uncaught Error: You cannot render a <Router> inside another <Router>. You should never have more than one in your app.
Upvotes: 7
Views: 12454
Reputation: 46
In your remote app you should wrap your header component inside a BrowserRouter, but the component that contains the BrowserRouter should not be exposed. In the example I am using react-router-dom v6.
Use another component (in my example it's Test.js, which will be used inside index.js, but this won't be exposed by the module federation and it is used just for local development of the remote app). Header is the component that you want to expose to module federation and use in the other app (as you have it already in your webpack config)
import React from 'react';
import {
BrowserRouter,
Routes,
Route,
} from "react-router-dom";
import Header from './Header';
const localRouter = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<div>home<Header></Header></div>}></Route>
<Route path="input"element={<div>input</div>}/>
</Routes>
</BrowserRouter>)
}
export default localRouter;
Then in your host app you can use the Header component in a similar way as you would in the remote app.
import React from 'react';
import {
BrowserRouter,
Routes,
Route,
Link
} from "react-router-dom";
const Header = React.lazy(() => import('header/Header'));
const HostApp = () => (
<>
<div>Hello, I'm the host app!</div>
<BrowserRouter>
<Routes>
<Route path="/" element={<div>home
<React.Suspense fallback="loading...">
<Header />
</React.Suspense>
</div>}></Route>
<Route path="input"element={<div>input</div>}/>
... some other routes
</Routes>
</BrowserRouter>
</>
);
export default HostApp;
The idea is the remote app needs the header to run inside a BrowserRouter for local development, but when used in a host application the Header component will use the BrowserRouter from the host application, since the BrowserRouter from the remote app is not exposed anywhere by the module federation.
Upvotes: 3