anmatika
anmatika

Reputation: 1711

Webpack module federation and react-router-dom

How to properly setup ModuleFederation and react-router-dom so that I can have

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

Answers (1)

OanaB
OanaB

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

Related Questions